diff --git a/.gitignore b/.gitignore
index 7f200c8b..855a7957 100644
--- a/.gitignore
+++ b/.gitignore
@@ -88,7 +88,7 @@ scripts/versioning/node_modules/
scripts/migration/node_modules/
tests/*
.ingress/
-CLAUDE.md
+
# Markdown lint cache
.md-cache
@@ -102,3 +102,6 @@ CLAUDE.md
# Temporary directory for link migration tracking
temporary/
+
+# Claude Code context and working files
+WORKING.md
diff --git a/.mintignore b/.mintignore
index 8bbc57c6..02d29f35 100644
--- a/.mintignore
+++ b/.mintignore
@@ -12,3 +12,7 @@ coverage/
# Temporary directory for link migration tracking
temporary/
+
+# Claude Code context and working files
+**/CLAUDE.md
+WORKING.md
diff --git a/CLAUDE.md b/CLAUDE.md
index ed1aa3c9..bc52f69b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -55,19 +55,17 @@ cd tests && npm test
## Architecture & Structure
### Documentation Organization
-The documentation follows a hierarchical structure configured in `docs.json`:
+The documentation follows a hierarchical structure configured in `docs.json`. The repo is a multi-product docs site; each product has its own top-level directory with versioned subdirectories.
-- **docs/documentation/** - Main documentation content
- - `concepts/` - Core concepts (accounts, gas, transactions, IBC)
- - `cosmos-sdk/` - SDK modules and protocol details
- - `smart-contracts/` - Precompiles and predeployed contracts
- - `integration/` - Integration guides and migration docs
- - `getting-started/` - Quick start guides and tooling
+#### Top-level product directories
-- **docs/api-reference/** - API documentation
- - `ethereum-json-rpc/` - RPC methods, OpenAPI spec, and explorer
-
-- **docs/changelog/** - Release notes auto-synced from cosmos/evm
+- **`ibc/`** — IBC (Inter-Blockchain Communication) docs. Versions: `next/` (active), `v10.1.x`, `v8.5.x`, `v7.8.x`, `v6.3.x`, `v5.4.x`, `v4.6.x`, `v0.2.0`
+- **`evm/`** — Cosmos EVM docs. Versions: `next/`, `v0.5.0`, `v0.4.x`
+- **`sdk/`** — Cosmos SDK docs. Versions: `next/`, `v0.53`, `v0.50`, `v0.47`
+- **`hub/`** — Cosmos Hub docs. Versions: `v25/`
+- **`cometbft/`** — CometBFT docs. Versions: `v0.38/`, `v0.37/`
+- **`skip-go/`** — Skip Go docs (unversioned, flat structure)
+- **`enterprise/`** — Enterprise docs
### Key Technical Components
@@ -106,10 +104,81 @@ When updating documentation:
- **Precompile Addresses**: Fixed addresses documented in tests/README.md
- **Network Endpoints**: Configure in tests/config.js for testing
+## Internal Link Format
+
+All internal links in MDX files must use absolute root-relative paths **without** the `.mdx` extension:
+
+```text
+✅ /sdk/next/changelog/release-notes
+❌ ./release-notes.mdx
+❌ ../changelog/release-notes.mdx
+```
+
+Mintlify resolves links by URL path, not filesystem path. Relative or extension-suffixed links will break in production.
+
+## File Operations Checklist
+
+When **adding**, **deleting**, **moving**, or **renaming** any `.mdx` file:
+
+1. **Update `docs.json`** — Add, remove, or update the page entry in the navigation structure. Every page must be registered to appear in the sidebar.
+2. **Add redirects** — For deleted, renamed, or moved pages, add a redirect in `docs.json` so existing links and search results don't 404.
+3. **Fix backlinks** — Search for all internal links pointing to the old path and update them to the new path.
+
+```bash
+# Find broken internal links
+npx mint broken-links
+```
+
+### Redirects in `docs.json`
+
+Redirects are defined as a top-level `"redirects"` array in `docs.json`. Each entry has a `source` and `destination`, both as root-relative paths without `.mdx` extensions:
+
+```json
+"redirects": [
+ {
+ "source": "/ibc/next/old-page-name",
+ "destination": "/ibc/next/new-page-name"
+ }
+]
+```
+
+Wildcards are supported:
+
+```json
+{ "source": "/ibc/beta/:slug*", "destination": "/ibc/next/:slug*" }
+```
+
+**Constraints:** Redirects cannot include URL anchors (`#anchor`) or query parameters (`?key=value`). Avoid circular redirects.
+
+### Versioning in `docs.json` and `versions.json`
+
+**`docs.json`** controls the version *dropdown UI* shown in the sidebar. Each product is a `dropdown` entry inside `navigation`, with a `versions` array. Each version entry contains its own `tabs`, `groups`, and `pages`:
+
+```json
+{
+ "dropdown": "IBC",
+ "versions": [
+ {
+ "version": "next",
+ "tabs": [ ... ]
+ },
+ {
+ "version": "v10.1.x",
+ "tabs": [ ... ]
+ }
+ ]
+}
+```
+
+**`versions.json`** is a *custom script config* (not a Mintlify-native file). It is used by the scripts in `scripts/versioning/` to manage changelog syncing, version freezing, and related automation. It defines each product's available versions, default version, upstream GitHub repository, and changelog path. It does not affect the live docs UI directly — changes here only affect what the scripts do.
+
+## Session State
+
+`WORKING.md` in the repo root tracks active work, recent changes, and known issues for the current session. It is gitignored and mintignored. Check it at the start of any session to understand what was last being worked on.
+
## Important Notes
- All documentation files use MDX format with Mintlify-specific components
-- Navigation structure must be updated in `docs.json` when adding new pages
- Interactive RPC documentation is generated from the source `methods.mdx` file
- Test findings in `tests/README.md` track documentation accuracy against implementation
- Use relative imports for snippets and components (e.g., `/snippets/icons.mdx`)
\ No newline at end of file
diff --git a/docs.json b/docs.json
index 8658da51..28a2ca46 100644
--- a/docs.json
+++ b/docs.json
@@ -1708,8 +1708,8 @@
{
"group": "Core Concepts",
"pages": [
- "ibc/v10.1.x/ibc/best-practices",
"ibc/v10.1.x/ibc/integration",
+ "ibc/v10.1.x/ibc/best-practices",
"ibc/v10.1.x/ibc/permissioning",
"ibc/v10.1.x/ibc/relayer"
]
@@ -1717,10 +1717,10 @@
{
"group": "Apps",
"pages": [
+ "ibc/v10.1.x/ibc/apps/ibcv2apps",
"ibc/v10.1.x/ibc/apps/apps",
- "ibc/v10.1.x/ibc/apps/bindports",
"ibc/v10.1.x/ibc/apps/ibcmodule",
- "ibc/v10.1.x/ibc/apps/ibcv2apps",
+ "ibc/v10.1.x/ibc/apps/bindports",
"ibc/v10.1.x/ibc/apps/keeper",
"ibc/v10.1.x/ibc/apps/packets_acks",
"ibc/v10.1.x/ibc/apps/routing",
@@ -1730,19 +1730,19 @@
{
"group": "Middleware",
"pages": [
+ "ibc/v10.1.x/ibc/middleware/overview",
"ibc/v10.1.x/ibc/middleware/develop",
"ibc/v10.1.x/ibc/middleware/developIBCv2",
- "ibc/v10.1.x/ibc/middleware/integration",
- "ibc/v10.1.x/ibc/middleware/overview"
+ "ibc/v10.1.x/ibc/middleware/integration"
]
},
{
"group": "Upgrades",
"pages": [
- "ibc/v10.1.x/ibc/upgrades/developer-guide",
- "ibc/v10.1.x/ibc/upgrades/genesis-restart",
"ibc/v10.1.x/ibc/upgrades/intro",
- "ibc/v10.1.x/ibc/upgrades/quick-guide"
+ "ibc/v10.1.x/ibc/upgrades/quick-guide",
+ "ibc/v10.1.x/ibc/upgrades/developer-guide",
+ "ibc/v10.1.x/ibc/upgrades/genesis-restart"
]
}
]
@@ -1782,32 +1782,32 @@
{
"group": "Developer Guide",
"pages": [
+ "ibc/v10.1.x/light-clients/developer-guide/overview",
+ "ibc/v10.1.x/light-clients/developer-guide/light-client-module",
"ibc/v10.1.x/light-clients/developer-guide/client-state",
"ibc/v10.1.x/light-clients/developer-guide/consensus-state",
- "ibc/v10.1.x/light-clients/developer-guide/light-client-module",
- "ibc/v10.1.x/light-clients/developer-guide/overview",
+ "ibc/v10.1.x/light-clients/developer-guide/updates-and-misbehaviour",
+ "ibc/v10.1.x/light-clients/developer-guide/upgrades",
"ibc/v10.1.x/light-clients/developer-guide/proofs",
"ibc/v10.1.x/light-clients/developer-guide/proposals",
- "ibc/v10.1.x/light-clients/developer-guide/setup",
- "ibc/v10.1.x/light-clients/developer-guide/updates-and-misbehaviour",
- "ibc/v10.1.x/light-clients/developer-guide/upgrades"
+ "ibc/v10.1.x/light-clients/developer-guide/setup"
]
},
{
"group": "Localhost",
"pages": [
+ "ibc/v10.1.x/light-clients/localhost/overview",
+ "ibc/v10.1.x/light-clients/localhost/integration",
"ibc/v10.1.x/light-clients/localhost/client-state",
"ibc/v10.1.x/light-clients/localhost/connection",
- "ibc/v10.1.x/light-clients/localhost/integration",
- "ibc/v10.1.x/light-clients/localhost/overview",
"ibc/v10.1.x/light-clients/localhost/state-verification"
]
},
{
"group": "Solo Machine",
"pages": [
- "ibc/v10.1.x/light-clients/solomachine/concepts",
"ibc/v10.1.x/light-clients/solomachine/solomachine",
+ "ibc/v10.1.x/light-clients/solomachine/concepts",
"ibc/v10.1.x/light-clients/solomachine/state",
"ibc/v10.1.x/light-clients/solomachine/state_transitions"
]
@@ -1821,15 +1821,15 @@
{
"group": "WASM",
"pages": [
- "ibc/v10.1.x/light-clients/wasm/client",
+ "ibc/v10.1.x/light-clients/wasm/overview",
"ibc/v10.1.x/light-clients/wasm/concepts",
- "ibc/v10.1.x/light-clients/wasm/contracts",
- "ibc/v10.1.x/light-clients/wasm/events",
- "ibc/v10.1.x/light-clients/wasm/governance",
"ibc/v10.1.x/light-clients/wasm/integration",
"ibc/v10.1.x/light-clients/wasm/messages",
- "ibc/v10.1.x/light-clients/wasm/migrations",
- "ibc/v10.1.x/light-clients/wasm/overview"
+ "ibc/v10.1.x/light-clients/wasm/governance",
+ "ibc/v10.1.x/light-clients/wasm/events",
+ "ibc/v10.1.x/light-clients/wasm/contracts",
+ "ibc/v10.1.x/light-clients/wasm/client",
+ "ibc/v10.1.x/light-clients/wasm/migrations"
]
}
]
@@ -1840,15 +1840,15 @@
{
"group": "Interchain Accounts",
"pages": [
- "ibc/v10.1.x/apps/interchain-accounts/active-channels",
- "ibc/v10.1.x/apps/interchain-accounts/auth-modules",
- "ibc/v10.1.x/apps/interchain-accounts/client",
+ "ibc/v10.1.x/apps/interchain-accounts/overview",
"ibc/v10.1.x/apps/interchain-accounts/development",
+ "ibc/v10.1.x/apps/interchain-accounts/auth-modules",
"ibc/v10.1.x/apps/interchain-accounts/integration",
"ibc/v10.1.x/apps/interchain-accounts/messages",
- "ibc/v10.1.x/apps/interchain-accounts/overview",
"ibc/v10.1.x/apps/interchain-accounts/parameters",
"ibc/v10.1.x/apps/interchain-accounts/tx-encoding",
+ "ibc/v10.1.x/apps/interchain-accounts/client",
+ "ibc/v10.1.x/apps/interchain-accounts/active-channels",
{
"group": "Legacy",
"pages": [
@@ -1862,16 +1862,16 @@
{
"group": "Transfer",
"pages": [
- "ibc/v10.1.x/apps/transfer/IBCv2-transfer",
- "ibc/v10.1.x/apps/transfer/authorizations",
- "ibc/v10.1.x/apps/transfer/client",
- "ibc/v10.1.x/apps/transfer/events",
+ "ibc/v10.1.x/apps/transfer/overview",
+ "ibc/v10.1.x/apps/transfer/state",
+ "ibc/v10.1.x/apps/transfer/state-transitions",
"ibc/v10.1.x/apps/transfer/messages",
+ "ibc/v10.1.x/apps/transfer/events",
"ibc/v10.1.x/apps/transfer/metrics",
- "ibc/v10.1.x/apps/transfer/overview",
"ibc/v10.1.x/apps/transfer/params",
- "ibc/v10.1.x/apps/transfer/state",
- "ibc/v10.1.x/apps/transfer/state-transitions"
+ "ibc/v10.1.x/apps/transfer/authorizations",
+ "ibc/v10.1.x/apps/transfer/client",
+ "ibc/v10.1.x/apps/transfer/IBCv2-transfer"
]
}
]
@@ -1882,20 +1882,16 @@
{
"group": "Callbacks",
"pages": [
- "ibc/v10.1.x/middleware/callbacks/callbacks-IBCv2",
- "ibc/v10.1.x/middleware/callbacks/end-users",
- "ibc/v10.1.x/middleware/callbacks/events",
- "ibc/v10.1.x/middleware/callbacks/gas",
+ "ibc/v10.1.x/middleware/callbacks/overview",
"ibc/v10.1.x/middleware/callbacks/integration",
"ibc/v10.1.x/middleware/callbacks/interfaces",
- "ibc/v10.1.x/middleware/callbacks/overview"
+ "ibc/v10.1.x/middleware/callbacks/events",
+ "ibc/v10.1.x/middleware/callbacks/end-users",
+ "ibc/v10.1.x/middleware/callbacks/gas",
+ "ibc/v10.1.x/middleware/callbacks/callbacks-IBCv2"
]
}
]
- },
- {
- "group": "Security",
- "pages": []
}
]
},
@@ -4299,10 +4295,10 @@
{
"group": "Apps",
"pages": [
+ "ibc/next/ibc/apps/ibcv2apps",
"ibc/next/ibc/apps/apps",
- "ibc/next/ibc/apps/bindports",
"ibc/next/ibc/apps/ibcmodule",
- "ibc/next/ibc/apps/ibcv2apps",
+ "ibc/next/ibc/apps/bindports",
"ibc/next/ibc/apps/keeper",
"ibc/next/ibc/apps/packets_acks",
"ibc/next/ibc/apps/routing",
@@ -4312,19 +4308,19 @@
{
"group": "Middleware",
"pages": [
+ "ibc/next/ibc/middleware/overview",
"ibc/next/ibc/middleware/develop",
"ibc/next/ibc/middleware/developIBCv2",
- "ibc/next/ibc/middleware/integration",
- "ibc/next/ibc/middleware/overview"
+ "ibc/next/ibc/middleware/integration"
]
},
{
"group": "Upgrades",
"pages": [
- "ibc/next/ibc/upgrades/developer-guide",
- "ibc/next/ibc/upgrades/genesis-restart",
"ibc/next/ibc/upgrades/intro",
- "ibc/next/ibc/upgrades/quick-guide"
+ "ibc/next/ibc/upgrades/quick-guide",
+ "ibc/next/ibc/upgrades/developer-guide",
+ "ibc/next/ibc/upgrades/genesis-restart"
]
}
]
@@ -4366,32 +4362,32 @@
{
"group": "Developer Guide",
"pages": [
+ "ibc/next/light-clients/developer-guide/overview",
+ "ibc/next/light-clients/developer-guide/light-client-module",
"ibc/next/light-clients/developer-guide/client-state",
"ibc/next/light-clients/developer-guide/consensus-state",
- "ibc/next/light-clients/developer-guide/light-client-module",
- "ibc/next/light-clients/developer-guide/overview",
+ "ibc/next/light-clients/developer-guide/updates-and-misbehaviour",
+ "ibc/next/light-clients/developer-guide/upgrades",
"ibc/next/light-clients/developer-guide/proofs",
"ibc/next/light-clients/developer-guide/proposals",
- "ibc/next/light-clients/developer-guide/setup",
- "ibc/next/light-clients/developer-guide/updates-and-misbehaviour",
- "ibc/next/light-clients/developer-guide/upgrades"
+ "ibc/next/light-clients/developer-guide/setup"
]
},
{
"group": "Localhost",
"pages": [
+ "ibc/next/light-clients/localhost/overview",
+ "ibc/next/light-clients/localhost/integration",
"ibc/next/light-clients/localhost/client-state",
"ibc/next/light-clients/localhost/connection",
- "ibc/next/light-clients/localhost/integration",
- "ibc/next/light-clients/localhost/overview",
"ibc/next/light-clients/localhost/state-verification"
]
},
{
"group": "Solo Machine",
"pages": [
- "ibc/next/light-clients/solomachine/concepts",
"ibc/next/light-clients/solomachine/solomachine",
+ "ibc/next/light-clients/solomachine/concepts",
"ibc/next/light-clients/solomachine/state",
"ibc/next/light-clients/solomachine/state_transitions"
]
@@ -4405,15 +4401,15 @@
{
"group": "WASM",
"pages": [
- "ibc/next/light-clients/wasm/client",
+ "ibc/next/light-clients/wasm/overview",
"ibc/next/light-clients/wasm/concepts",
- "ibc/next/light-clients/wasm/contracts",
- "ibc/next/light-clients/wasm/events",
- "ibc/next/light-clients/wasm/governance",
"ibc/next/light-clients/wasm/integration",
"ibc/next/light-clients/wasm/messages",
- "ibc/next/light-clients/wasm/migrations",
- "ibc/next/light-clients/wasm/overview"
+ "ibc/next/light-clients/wasm/governance",
+ "ibc/next/light-clients/wasm/events",
+ "ibc/next/light-clients/wasm/contracts",
+ "ibc/next/light-clients/wasm/client",
+ "ibc/next/light-clients/wasm/migrations"
]
}
]
@@ -4424,15 +4420,15 @@
{
"group": "Interchain Accounts",
"pages": [
- "ibc/next/apps/interchain-accounts/active-channels",
- "ibc/next/apps/interchain-accounts/auth-modules",
- "ibc/next/apps/interchain-accounts/client",
+ "ibc/next/apps/interchain-accounts/overview",
"ibc/next/apps/interchain-accounts/development",
+ "ibc/next/apps/interchain-accounts/auth-modules",
"ibc/next/apps/interchain-accounts/integration",
"ibc/next/apps/interchain-accounts/messages",
- "ibc/next/apps/interchain-accounts/overview",
"ibc/next/apps/interchain-accounts/parameters",
"ibc/next/apps/interchain-accounts/tx-encoding",
+ "ibc/next/apps/interchain-accounts/client",
+ "ibc/next/apps/interchain-accounts/active-channels",
{
"group": "Legacy",
"pages": [
@@ -4446,16 +4442,16 @@
{
"group": "Transfer",
"pages": [
- "ibc/next/apps/transfer/IBCv2-transfer",
- "ibc/next/apps/transfer/authorizations",
- "ibc/next/apps/transfer/client",
- "ibc/next/apps/transfer/events",
+ "ibc/next/apps/transfer/overview",
+ "ibc/next/apps/transfer/state",
+ "ibc/next/apps/transfer/state-transitions",
"ibc/next/apps/transfer/messages",
+ "ibc/next/apps/transfer/events",
"ibc/next/apps/transfer/metrics",
- "ibc/next/apps/transfer/overview",
"ibc/next/apps/transfer/params",
- "ibc/next/apps/transfer/state",
- "ibc/next/apps/transfer/state-transitions"
+ "ibc/next/apps/transfer/authorizations",
+ "ibc/next/apps/transfer/client",
+ "ibc/next/apps/transfer/IBCv2-transfer"
]
}
]
@@ -4466,18 +4462,28 @@
{
"group": "Callbacks",
"pages": [
- "ibc/next/middleware/callbacks/callbacks-IBCv2",
- "ibc/next/middleware/callbacks/end-users",
- "ibc/next/middleware/callbacks/events",
- "ibc/next/middleware/callbacks/gas",
+ "ibc/next/middleware/callbacks/overview",
"ibc/next/middleware/callbacks/integration",
"ibc/next/middleware/callbacks/interfaces",
- "ibc/next/middleware/callbacks/overview",
- "ibc/next/middleware/packet-forward-middleware/example-usage",
- "ibc/next/middleware/packet-forward-middleware/integration",
+ "ibc/next/middleware/callbacks/events",
+ "ibc/next/middleware/callbacks/end-users",
+ "ibc/next/middleware/callbacks/gas",
+ "ibc/next/middleware/callbacks/callbacks-IBCv2"
+ ]
+ },
+ {
+ "group": "Packet Forward Middleware",
+ "pages": [
"ibc/next/middleware/packet-forward-middleware/overview",
- "ibc/next/middleware/rate-limit-middleware/integration",
+ "ibc/next/middleware/packet-forward-middleware/integration",
+ "ibc/next/middleware/packet-forward-middleware/example-usage"
+ ]
+ },
+ {
+ "group": "Rate Limit Middleware",
+ "pages": [
"ibc/next/middleware/rate-limit-middleware/overview",
+ "ibc/next/middleware/rate-limit-middleware/integration",
"ibc/next/middleware/rate-limit-middleware/setting-limits"
]
}
@@ -4491,6 +4497,263 @@
}
]
},
+ {
+ "tab": "ADRs",
+ "groups": [
+ {
+ "group": "Architecture Decision Records",
+ "pages": [
+ "ibc/next/architecture/README",
+ "ibc/next/architecture/adr-001-coin-source-tracing",
+ "ibc/next/architecture/adr-002-go-module-versioning",
+ "ibc/next/architecture/adr-003-ics27-acknowledgement",
+ "ibc/next/architecture/adr-004-ics29-lock-fee-module",
+ "ibc/next/architecture/adr-005-consensus-height-events",
+ "ibc/next/architecture/adr-006-02-client-refactor",
+ "ibc/next/architecture/adr-007-solomachine-signbytes",
+ "ibc/next/architecture/adr-008-app-caller-cbs",
+ "ibc/next/architecture/adr-009-v6-ics27-msgserver",
+ "ibc/next/architecture/adr-010-light-clients-as-sdk-modules",
+ "ibc/next/architecture/adr-011-transfer-total-escrow-state-entry",
+ "ibc/next/architecture/adr-015-ibc-packet-receiver",
+ "ibc/next/architecture/adr-025-ibc-passive-channels",
+ "ibc/next/architecture/adr-026-ibc-client-recovery-mechanisms",
+ "ibc/next/architecture/adr-027-ibc-wasm"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Specs",
+ "groups": [
+ {
+ "group": "IBC Specs (v1)",
+ "pages": [
+ {
+ "group": "App",
+ "pages": [
+ {
+ "group": "ics-020-fungible-token-transfer",
+ "pages": [
+ "ibc/next/spec/app/ics-020-fungible-token-transfer/README",
+ {
+ "group": "deprecated",
+ "pages": [
+ "ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/README"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "ics-027-interchain-accounts",
+ "pages": [
+ "ibc/next/spec/app/ics-027-interchain-accounts/README"
+ ]
+ },
+ {
+ "group": "ics-028-cross-chain-validation",
+ "pages": [
+ "ibc/next/spec/app/ics-028-cross-chain-validation/README",
+ "ibc/next/spec/app/ics-028-cross-chain-validation/data_structures",
+ "ibc/next/spec/app/ics-028-cross-chain-validation/methods",
+ "ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts",
+ "ibc/next/spec/app/ics-028-cross-chain-validation/system_model_and_properties",
+ "ibc/next/spec/app/ics-028-cross-chain-validation/technical_specification"
+ ]
+ },
+ {
+ "group": "ics-029-fee-payment",
+ "pages": [
+ "ibc/next/spec/app/ics-029-fee-payment/README"
+ ]
+ },
+ {
+ "group": "ics-030-middleware",
+ "pages": [
+ "ibc/next/spec/app/ics-030-middleware/README"
+ ]
+ },
+ {
+ "group": "ics-031-crosschain-queries",
+ "pages": [
+ "ibc/next/spec/app/ics-031-crosschain-queries/README"
+ ]
+ },
+ {
+ "group": "ics-100-atomic-swap",
+ "pages": [
+ "ibc/next/spec/app/ics-100-atomic-swap/README"
+ ]
+ },
+ {
+ "group": "ics-721-nft-transfer",
+ "pages": [
+ "ibc/next/spec/app/ics-721-nft-transfer/README"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Client",
+ "pages": [
+ "ibc/next/spec/client/Rollup-Integration-guide",
+ {
+ "group": "ics-006-solo-machine-client",
+ "pages": [
+ "ibc/next/spec/client/ics-006-solo-machine-client/README"
+ ]
+ },
+ {
+ "group": "ics-007-tendermint-client",
+ "pages": [
+ "ibc/next/spec/client/ics-007-tendermint-client/README"
+ ]
+ },
+ {
+ "group": "ics-008-wasm-client",
+ "pages": [
+ "ibc/next/spec/client/ics-008-wasm-client/README"
+ ]
+ },
+ {
+ "group": "ics-009-loopback-cilent",
+ "pages": [
+ "ibc/next/spec/client/ics-009-loopback-cilent/README"
+ ]
+ },
+ {
+ "group": "ics-010-grandpa-client",
+ "pages": [
+ "ibc/next/spec/client/ics-010-grandpa-client/README"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Core",
+ "pages": [
+ {
+ "group": "ics-002-client-semantics",
+ "pages": [
+ "ibc/next/spec/core/ics-002-client-semantics/README"
+ ]
+ },
+ {
+ "group": "ics-003-connection-semantics",
+ "pages": [
+ "ibc/next/spec/core/ics-003-connection-semantics/README",
+ "ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal"
+ ]
+ },
+ {
+ "group": "ics-004-channel-and-packet-semantics",
+ "pages": [
+ "ibc/next/spec/core/ics-004-channel-and-packet-semantics/README",
+ "ibc/next/spec/core/ics-004-channel-and-packet-semantics/UPGRADES"
+ ]
+ },
+ {
+ "group": "ics-005-port-allocation",
+ "pages": [
+ "ibc/next/spec/core/ics-005-port-allocation/README"
+ ]
+ },
+ {
+ "group": "ics-023-vector-commitments",
+ "pages": [
+ "ibc/next/spec/core/ics-023-vector-commitments/README"
+ ]
+ },
+ {
+ "group": "ics-024-host-requirements",
+ "pages": [
+ "ibc/next/spec/core/ics-024-host-requirements/README"
+ ]
+ },
+ {
+ "group": "ics-025-handler-interface",
+ "pages": [
+ "ibc/next/spec/core/ics-025-handler-interface/README"
+ ]
+ },
+ {
+ "group": "ics-026-routing-module",
+ "pages": [
+ "ibc/next/spec/core/ics-026-routing-module/README",
+ "ibc/next/spec/core/ics-026-routing-module/UPGRADES"
+ ]
+ },
+ {
+ "group": "ics-033-multi-hop",
+ "pages": [
+ "ibc/next/spec/core/ics-033-multi-hop/README"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Ics-001-ics-standard",
+ "pages": [
+ "ibc/next/spec/ics-001-ics-standard/README"
+ ]
+ },
+ {
+ "group": "Relayer",
+ "pages": [
+ {
+ "group": "ics-018-relayer-algorithms",
+ "pages": [
+ "ibc/next/spec/relayer/ics-018-relayer-algorithms/README"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "group": "IBC Specs (v2)",
+ "pages": [
+ "ibc/next/spec/IBC_V2/README",
+ {
+ "group": "Core",
+ "pages": [
+ {
+ "group": "ics-002-client-semantics",
+ "pages": [
+ "ibc/next/spec/IBC_V2/core/ics-002-client-semantics/README"
+ ]
+ },
+ {
+ "group": "ics-004-packet-semantics",
+ "pages": [
+ "ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER",
+ "ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET"
+ ]
+ },
+ {
+ "group": "ics-005-port-allocation",
+ "pages": [
+ "ibc/next/spec/IBC_V2/core/ics-005-port-allocation/README"
+ ]
+ },
+ {
+ "group": "ics-024-host-requirements",
+ "pages": [
+ "ibc/next/spec/IBC_V2/core/ics-024-host-requirements/README"
+ ]
+ },
+ {
+ "group": "ics-026-application-callbacks",
+ "pages": [
+ "ibc/next/spec/IBC_V2/core/ics-026-application-callbacks/README"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ },
{
"tab": "Release Notes",
"pages": [
diff --git a/ibc/CLAUDE.md b/ibc/CLAUDE.md
new file mode 100644
index 00000000..2e3a53ff
--- /dev/null
+++ b/ibc/CLAUDE.md
@@ -0,0 +1,172 @@
+# CLAUDE.md — IBC Docs Section
+
+This file provides guidance for working in the `ibc/` section of the IBC-docs Mintlify repo.
+
+## Overview
+
+This directory contains all IBC (Inter-Blockchain Communication) documentation for the docs site. It documents **ibc-go** — the canonical Go implementation of IBC — found at `../../../ibc-go/` (sibling repo).
+
+Navigation for all versions is configured in the root `docs.json`.
+
+## Protocol Context
+
+Since ibc-go v10, two protocol versions coexist in the same release:
+
+- **IBC Classic** — The original TAO (Transport, Authentication, Ordering) + APP layer architecture. Uses light clients, connections, channels, and ports.
+- **IBC v2** — A newer, simplified protocol. Separate from Classic; a connection uses one or the other, not both.
+
+For IBC Eureka (the canonical IBC v2 deployment connecting Cosmos chains and Ethereum), see `../skip-go/eureka/`.
+
+## Versioned Structure
+
+| Directory | Status |
+|--------------|----------------|
+| `next/` | Active — work here |
+| `v10.1.x/` | Frozen |
+| `v8.5.x/` | Frozen |
+| `v7.8.x/` | Frozen |
+| `v6.3.x/` | Frozen |
+| `v5.4.x/` | Frozen |
+| `v4.6.x/` | Frozen |
+| `v0.2.0/` | Frozen |
+
+**Always work in `next/`.** Frozen versions should not be edited except to fix critical errors.
+
+## `next/` Directory Structure
+
+```
+next/
+├── index.mdx # Section landing page
+├── intro.mdx # IBC-Go introduction and high-level overview
+├── security-audits.mdx # Security audit history
+│
+├── ibc/ # Core IBC protocol (TAO layer)
+│ ├── overview.mdx
+│ ├── integration.mdx
+│ ├── best-practices.mdx
+│ ├── relayer.mdx
+│ ├── permissioning.mdx
+│ ├── apps/ # Building IBC application modules
+│ │ ├── apps.mdx
+│ │ ├── address-codec.mdx
+│ │ ├── bindports.mdx
+│ │ ├── ibcmodule.mdx # IBCModule interface
+│ │ ├── ibcv2apps.mdx # IBCv2 app development
+│ │ ├── keeper.mdx
+│ │ ├── packets_acks.mdx
+│ │ └── routing.mdx
+│ ├── middleware/ # Writing custom middleware
+│ │ ├── overview.mdx
+│ │ ├── develop.mdx
+│ │ ├── developIBCv2.mdx
+│ │ └── integration.mdx
+│ └── upgrades/ # Chain upgrade handling
+│ ├── intro.mdx
+│ ├── quick-guide.mdx
+│ ├── developer-guide.mdx
+│ └── genesis-restart.mdx
+│
+├── apps/ # IBC application modules (ICS standards)
+│ ├── transfer/ # ICS-20 fungible token transfer
+│ │ ├── overview.mdx
+│ │ ├── state.mdx
+│ │ ├── state-transitions.mdx
+│ │ ├── messages.mdx
+│ │ ├── events.mdx
+│ │ ├── params.mdx
+│ │ ├── client.mdx
+│ │ ├── authorizations.mdx
+│ │ └── IBCv2-transfer.mdx
+│ └── interchain-accounts/ # ICS-27 Interchain Accounts (ICA)
+│ ├── overview.mdx
+│ ├── integration.mdx
+│ ├── auth-modules.mdx
+│ ├── messages.mdx
+│ ├── tx-encoding.mdx
+│ ├── parameters.mdx
+│ ├── active-channels.mdx
+│ ├── client.mdx
+│ ├── development.mdx
+│ └── legacy/ # Legacy ICA API (pre-v7)
+│ ├── auth-modules.mdx
+│ ├── integration.mdx
+│ └── keeper-api.mdx
+│
+├── light-clients/ # Light client implementations
+│ ├── proposals.mdx # Governance proposals for light clients
+│ ├── tendermint/
+│ │ └── overview.mdx
+│ ├── solomachine/
+│ │ ├── concepts.mdx
+│ │ ├── solomachine.mdx
+│ │ ├── state.mdx
+│ │ └── state_transitions.mdx
+│ ├── localhost/
+│ ├── wasm/ # 08-wasm light client
+│ │ ├── overview.mdx
+│ │ ├── concepts.mdx
+│ │ ├── contracts.mdx
+│ │ ├── integration.mdx
+│ │ ├── governance.mdx
+│ │ ├── messages.mdx
+│ │ ├── events.mdx
+│ │ ├── client.mdx
+│ │ └── migrations.mdx
+│ └── developer-guide/ # Building custom light clients
+│ ├── overview.mdx
+│ ├── setup.mdx
+│ ├── client-state.mdx
+│ ├── consensus-state.mdx
+│ ├── light-client-module.mdx
+│ ├── proofs.mdx
+│ ├── proposals.mdx
+│ ├── updates-and-misbehaviour.mdx
+│ └── upgrades.mdx
+│
+├── middleware/ # Middleware modules
+│ ├── callbacks/ # IBC Callbacks middleware
+│ │ ├── overview.mdx
+│ │ ├── integration.mdx
+│ │ ├── interfaces.mdx
+│ │ ├── end-users.mdx
+│ │ ├── gas.mdx
+│ │ ├── events.mdx
+│ │ └── callbacks-IBCv2.mdx
+│ ├── packet-forward-middleware/
+│ │ ├── overview.mdx
+│ │ ├── integration.mdx
+│ │ └── example-usage.mdx
+│ └── rate-limit-middleware/
+│ ├── overview.mdx
+│ ├── integration.mdx
+│ └── setting-limits.mdx
+│
+├── migrations/ # Version migration guides
+│ ├── sdk-to-v1.mdx
+│ ├── v1-to-v2.mdx
+│ ├── v2-to-v3.mdx
+│ ├── v3-to-v4.mdx
+│ ├── v4-to-v5.mdx
+│ ├── v5-to-v6.mdx
+│ ├── v6-to-v7.mdx
+│ ├── v7-to-v7_1.mdx
+│ ├── v7-to-v8.mdx
+│ ├── v7_2-to-v7_3.mdx
+│ ├── v8-to-v8_1.mdx
+│ ├── v8_1-to-v10.mdx
+│ ├── v10-to-v11.mdx
+│ ├── support-denoms-with-slashes.mdx
+│ ├── support-stackbuilder.mdx
+│ └── migration.template.mdx # Template for new migration guides
+│
+└── changelog/
+ └── release-notes.mdx # Release notes (auto-synced from cosmos/ibc-go)
+```
+
+## Key Notes
+
+- **Navigation**: All pages must be registered in the root `docs.json` to appear in the sidebar. Adding a new file without updating `docs.json` will make it unreachable.
+- **IBCv2 content**: Many sections have parallel IBCv2 pages (e.g., `ibcv2apps.mdx`, `callbacks-IBCv2.mdx`, `IBCv2-transfer.mdx`). Keep Classic and v2 docs in sync when updating.
+- **Legacy ICA**: The `apps/interchain-accounts/legacy/` subdirectory documents the pre-v7 ICA API. Do not update these files; they are frozen references.
+- **Changelogs**: `changelog/release-notes.mdx` is managed by the versioning scripts in `../scripts/versioning/`. Don't edit it manually.
+- **Images**: Shared images are in `../images/` (sibling to `next/`).
diff --git a/ibc/next/architecture/README.mdx b/ibc/next/architecture/README.mdx
new file mode 100644
index 00000000..05760b6d
--- /dev/null
+++ b/ibc/next/architecture/README.mdx
@@ -0,0 +1,48 @@
+---
+sidebar_position: 1
+---
+
+# Architecture Decision Records (ADR)
+
+This is a location to record all high-level architecture decisions in the ibc-go project.
+
+You can read more about the ADR concept in this [blog post](https://product.reverb.com/documenting-architecture-decisions-the-reverb-way-a3563bb24bd0#.78xhdix6t).
+
+An ADR should provide:
+
+- Context on the relevant goals and the current state
+- Proposed changes to achieve the goals
+- Summary of pros and cons
+- References
+- Changelog
+
+Note the distinction between an ADR and a spec. The ADR provides the context, intuition, reasoning, and
+justification for a change in architecture, or for the architecture of something
+new. The spec is much more compressed and streamlined summary of everything as
+it is or should be.
+
+If recorded decisions turned out to be lacking, convene a discussion, record the new decisions here, and then modify the code to match.
+
+Note the context/background should be written in the present tense.
+
+To suggest an ADR, please make use of the [ADR template](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr.template.md) provided.
+
+## Table of Contents
+
+| ADR \# | Description | Status |
+| ------ | ----------- | ------ |
+| [001](/ibc/next/architecture/adr-001-coin-source-tracing) | ICS-20 coin denomination format | Accepted, Implemented |
+| [002](/ibc/next/architecture/adr-002-go-module-versioning) | Go module versioning | Accepted |
+| [003](/ibc/next/architecture/adr-003-ics27-acknowledgement) | ICS27 acknowledgement format | Accepted |
+| [004](/ibc/next/architecture/adr-004-ics29-lock-fee-module) | ICS29 module locking upon escrow out of balance | Accepted |
+| [005](/ibc/next/architecture/adr-005-consensus-height-events) | `UpdateClient` events - `ClientState` consensus heights | Accepted |
+| [006](/ibc/next/architecture/adr-006-02-client-refactor) | ICS02 client refactor | Accepted |
+| [007](/ibc/next/architecture/adr-007-solomachine-signbytes) | ICS06 Solo machine sign bytes | Accepted |
+| [008](/ibc/next/architecture/adr-008-app-caller-cbs) | Callback to IBC Actors | Accepted |
+| [009](/ibc/next/architecture/adr-009-v6-ics27-msgserver) | ICS27 message server addition | Accepted |
+| [010](/ibc/next/architecture/adr-010-light-clients-as-sdk-modules) | IBC light clients as SDK modules | Accepted |
+| [011](/ibc/next/architecture/adr-011-transfer-total-escrow-state-entry) | ICS20 state entry for total amount of tokens in escrow | Accepted |
+| [015](/ibc/next/architecture/adr-015-ibc-packet-receiver) | IBC Packet Routing | Accepted |
+| [025](/ibc/next/architecture/adr-025-ibc-passive-channels) | IBC passive channels | Deprecated |
+| [026](/ibc/next/architecture/adr-026-ibc-client-recovery-mechanisms) | IBC client recovery mechanisms | Accepted |
+| [027](/ibc/next/architecture/adr-027-ibc-wasm) | Wasm based light clients | Accepted |
diff --git a/ibc/next/architecture/adr-001-coin-source-tracing.mdx b/ibc/next/architecture/adr-001-coin-source-tracing.mdx
new file mode 100644
index 00000000..a4eb4e47
--- /dev/null
+++ b/ibc/next/architecture/adr-001-coin-source-tracing.mdx
@@ -0,0 +1,377 @@
+---
+title: "ADR 001: Coin Source Tracing"
+---
+
+## Changelog
+
+- 09-07-2020: Initial Draft
+- 11-08-2020: Implementation changes
+
+## Status
+
+Accepted, Implemented
+
+## Context
+
+The specification for IBC cross-chain fungible token transfers
+([ICS20](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer)), needs to
+be aware of the origin of any token denomination in order to relay a `Packet` which contains the sender
+and recipient addresses in the
+[`FungibleTokenPacketData`](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures).
+
+The Packet relay sending works based in 2 cases (per
+[specification](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#packet-relay) and [Colin Axnér](https://github.com/colin-axner)'s description):
+
+1. Sender chain is acting as the source zone. The coins are transferred
+to an escrow address (i.e locked) on the sender chain and then transferred
+to the receiving chain through IBC TAO logic. It is expected that the
+receiving chain will mint vouchers to the receiving address.
+
+2. Sender chain is acting as the sink zone. The coins (vouchers) are burned
+on the sender chain and then transferred to the receiving chain through IBC
+TAO logic. It is expected that the receiving chain, which had previously
+sent the original denomination, will unescrow the fungible token and send
+it to the receiving address.
+
+Another way of thinking of source and sink zones is through the token's
+timeline. Each send to any chain other than the one it was previously
+received from is a movement forwards in the token's timeline. This causes
+trace to be added to the token's history and the destination port and
+destination channel to be prefixed to the denomination. In these instances
+the sender chain is acting as the source zone. When the token is sent back
+to the chain it previously received from, the prefix is removed. This is
+a backwards movement in the token's timeline and the sender chain
+is acting as the sink zone.
+
+### Example
+
+Assume the following channel connections exist and that all channels use the port ID `transfer`:
+
+- chain `A` has channels with chain `B` and chain `C` with the IDs `channelToB` and `channelToC`, respectively
+- chain `B` has channels with chain `A` and chain `C` with the IDs `channelToA` and `channelToC`, respectively
+- chain `C` has channels with chain `A` and chain `B` with the IDs `channelToA` and `channelToB`, respectively
+
+These steps of transfer between chains occur in the following order: `A -> B -> C -> A -> C`. In particular:
+
+1. `A -> B`: sender chain is source zone. `A` sends packet with `denom` (escrowed on `A`), `B` receives `denom` and mints and sends voucher `transfer/channelToA/denom` to recipient.
+2. `B -> C`: sender chain is source zone. `B` sends packet with `transfer/channelToA/denom` (escrowed on `B`), `C` receives `transfer/channelToA/denom` and mints and sends voucher `transfer/channelToB/transfer/channelToA/denom` to recipient.
+3. `C -> A`: sender chain is source zone. `C` sends packet with `transfer/channelToB/transfer/channelToA/denom` (escrowed on `C`), `A` receives `transfer/channelToB/transfer/channelToA/denom` and mints and sends voucher `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` to recipient.
+4. `A -> C`: sender chain is sink zone. `A` sends packet with `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom` (burned on `A`), `C` receives `transfer/channelToC/transfer/channelToB/transfer/channelToA/denom`, and unescrows and sends `transfer/channelToB/transfer/channelToA/denom` to recipient.
+
+The token has a final denomination on chain `C` of `transfer/channelToB/transfer/channelToA/denom`, where `transfer/channelToB/transfer/channelToA` is the trace information.
+
+In this context, upon a receive of a cross-chain fungible token transfer, if the sender chain is the source of the token, the protocol prefixes the denomination with the port and channel identifiers in the following format:
+
+```typescript
+prefix + denom = {destPortN}/{destChannelN}/.../{destPort0}/{destChannel0}/denom
+```
+
+Example: transferring `100 uatom` from port `HubPort` and channel `HubChannel` on the Hub to
+Ethermint's port `EthermintPort` and channel `EthermintChannel` results in `100
+EthermintPort/EthermintChannel/uatom`, where `EthermintPort/EthermintChannel/uatom` is the new
+denomination on the receiving chain.
+
+In the case those tokens are transferred back to the Hub (i.e the **source** chain), the prefix is
+trimmed and the token denomination updated to the original one.
+
+### Problem
+
+The problem of adding additional information to the coin denomination is twofold:
+
+1. The ever increasing length if tokens are transferred to zones other than the source:
+
+If a token is transferred `n` times via IBC to a sink chain, the token denom will contain `n` pairs
+of prefixes, as shown on the format example above. This poses a problem because, while port and
+channel identifiers have a maximum length of 64 each, the SDK `Coin` type only accepts denoms up to
+64 characters. Thus, a single cross-chain token, which again, is composed by the port and channels
+identifiers plus the base denomination, can exceed the length validation for the SDK `Coins`.
+
+This can result in undesired behaviours such as tokens not being able to be transferred to multiple
+sink chains if the denomination exceeds the length or unexpected `panics` due to denomination
+validation failing on the receiving chain.
+
+2. The existence of special characters and uppercase letters on the denomination:
+
+In the SDK every time a `Coin` is initialized through the constructor function `NewCoin`, a validation
+of a coin's denom is performed according to a
+[Regex](https://github.com/cosmos/cosmos-sdk/blob/a940214a4923a3bf9a9161cd14bd3072299cd0c9/types/coin.go#L583),
+where only lowercase alphanumeric characters are accepted. While this is desirable for native denominations
+to keep a clean UX, it presents a challenge for IBC as ports and channels might be randomly
+generated with special and uppercase characters as per the [ICS 024 - Host
+Requirements](https://github.com/cosmos/ibc/tree/master/spec/core/ics-024-host-requirements#paths-identifiers-separators)
+specification.
+
+## Decision
+
+The issues outlined above, are applicable only to SDK-based chains, and thus the proposed solution
+are do not require specification changes that would result in modification to other implementations
+of the ICS20 spec.
+
+Instead of adding the identifiers on the coin denomination directly, the proposed solution hashes
+the denomination prefix in order to get a consistent length for all the cross-chain fungible tokens.
+
+This will be used for internal storage only, and when transferred via IBC to a different chain, the
+denomination specified on the packed data will be the full prefix path of the identifiers needed to
+trace the token back to the originating chain, as specified on ICS20.
+
+The new proposed format will be the following:
+
+```go
+ibcDenom = "ibc/" + hash(trace path + "/" + base denom)
+```
+
+The hash function will be a SHA256 hash of the fields of the `DenomTrace`:
+
+```protobuf
+// DenomTrace contains the base denomination for ICS20 fungible tokens and the source tracing
+// information
+message DenomTrace {
+ // chain of port/channel identifiers used for tracing the source of the fungible token
+ string path = 1;
+ // base denomination of the relayed fungible token
+ string base_denom = 2;
+}
+```
+
+The `IBCDenom` function constructs the `Coin` denomination used when creating the ICS20 fungible token packet data:
+
+```go
+// Hash returns the hex bytes of the SHA256 hash of the DenomTrace fields using the following formula:
+//
+// hash = sha256(tracePath + "/" + baseDenom)
+func (dt DenomTrace) Hash() tmbytes.HexBytes {
+ return tmhash.Sum(dt.Path + "/" + dt.BaseDenom)
+}
+
+// IBCDenom a coin denomination for an ICS20 fungible token in the format 'ibc/{hash(tracePath + baseDenom)}'.
+// If the trace is empty, it will return the base denomination.
+func (dt DenomTrace) IBCDenom() string {
+ if dt.Path != "" {
+ return fmt.Sprintf("ibc/%s", dt.Hash())
+ }
+ return dt.BaseDenom
+}
+```
+
+### `x/ibc-transfer` Changes
+
+In order to retrieve the trace information from an IBC denomination, a lookup table needs to be
+added to the `ibc-transfer` module. These values need to also be persisted between upgrades, meaning
+that a new `[]DenomTrace` `GenesisState` field state needs to be added to the module:
+
+```go
+// GetDenomTrace retrieves the full identifiers trace and base denomination from the store.
+func (k Keeper) GetDenomTrace(ctx Context, denomTraceHash []byte) (DenomTrace, bool) {
+ store := ctx.KVStore(k.storeKey)
+ bz := store.Get(types.KeyDenomTrace(traceHash))
+ if bz == nil {
+ return &DenomTrace, false
+ }
+
+ var denomTrace DenomTrace
+ k.cdc.MustUnmarshalBinaryBare(bz, &denomTrace)
+ return denomTrace, true
+}
+
+// HasDenomTrace checks if a the key with the given trace hash exists on the store.
+func (k Keeper) HasDenomTrace(ctx Context, denomTraceHash []byte) bool {
+ store := ctx.KVStore(k.storeKey)
+ return store.Has(types.KeyTrace(denomTraceHash))
+}
+
+// SetDenomTrace sets a new {trace hash -> trace} pair to the store.
+func (k Keeper) SetDenomTrace(ctx Context, denomTrace DenomTrace) {
+ store := ctx.KVStore(k.storeKey)
+ bz := k.cdc.MustMarshalBinaryBare(&denomTrace)
+ store.Set(types.KeyTrace(denomTrace.Hash()), bz)
+}
+```
+
+The `MsgTransfer` will validate that the `Coin` denomination from the `Token` field contains a valid
+hash, if the trace info is provided, or that the base denominations matches:
+
+```go
+func (msg MsgTransfer) ValidateBasic() error {
+ // ...
+ return ValidateIBCDenom(msg.Token.Denom)
+}
+```
+
+```go
+// ValidateIBCDenom validates that the given denomination is either:
+//
+// - A valid base denomination (eg: 'uatom')
+// - A valid fungible token representation (i.e 'ibc/{hash}') per ADR 001 https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md
+func ValidateIBCDenom(denom string) error {
+ denomSplit := strings.SplitN(denom, "/", 2)
+
+ switch {
+ case strings.TrimSpace(denom) == "",
+ len(denomSplit) == 1 && denomSplit[0] == "ibc",
+ len(denomSplit) == 2 && (denomSplit[0] != "ibc" || strings.TrimSpace(denomSplit[1]) == ""):
+ return sdkerrors.Wrapf(ErrInvalidDenomForTransfer, "denomination should be prefixed with the format 'ibc/{hash(trace + \"/\" + %s)}'", denom)
+
+ case denomSplit[0] == denom && strings.TrimSpace(denom) != "":
+ return sdk.ValidateDenom(denom)
+ }
+
+ if _, err := ParseHexHash(denomSplit[1]); err != nil {
+ return Wrapf(err, "invalid denom trace hash %s", denomSplit[1])
+ }
+
+ return nil
+}
+```
+
+The denomination trace info only needs to be updated when token is received:
+
+- Receiver is **source** chain: The receiver created the token and must have the trace lookup already stored (if necessary *ie* native token case wouldn't need a lookup).
+- Receiver is **not source** chain: Store the received info. For example, during step 1, when chain `B` receives `transfer/channelToA/denom`.
+
+```go
+// SendTransfer
+// ...
+
+ fullDenomPath := token.Denom
+
+// deconstruct the token denomination into the denomination trace info
+// to determine if the sender is the source chain
+if strings.HasPrefix(token.Denom, "ibc/") {
+ fullDenomPath, err = k.DenomPathFromHash(ctx, token.Denom)
+ if err != nil {
+ return err
+ }
+}
+
+if types.SenderChainIsSource(sourcePort, sourceChannel, fullDenomPath) {
+//...
+```
+
+```go
+// DenomPathFromHash returns the full denomination path prefix from an ibc denom with a hash
+// component.
+func (k Keeper) DenomPathFromHash(ctx sdk.Context, denom string) (string, error) {
+ hexHash := denom[4:]
+ hash, err := ParseHexHash(hexHash)
+ if err != nil {
+ return "", Wrap(ErrInvalidDenomForTransfer, err.Error())
+ }
+
+ denomTrace, found := k.GetDenomTrace(ctx, hash)
+ if !found {
+ return "", Wrap(ErrTraceNotFound, hexHash)
+ }
+
+ fullDenomPath := denomTrace.GetFullDenomPath()
+ return fullDenomPath, nil
+}
+```
+
+```go
+// OnRecvPacket
+// ...
+
+// This is the prefix that would have been prefixed to the denomination
+// on sender chain IF and only if the token originally came from the
+// receiving chain.
+//
+// NOTE: We use SourcePort and SourceChannel here, because the counterparty
+// chain would have prefixed with DestPort and DestChannel when originally
+// receiving this coin as seen in the "sender chain is the source" condition.
+if ReceiverChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) {
+ // sender chain is not the source, unescrow tokens
+
+ // remove prefix added by sender chain
+ voucherPrefix := types.GetDenomPrefix(packet.GetSourcePort(), packet.GetSourceChannel())
+ unprefixedDenom := data.Denom[len(voucherPrefix):]
+ token := sdk.NewCoin(unprefixedDenom, sdk.NewIntFromUint64(data.Amount))
+
+ // unescrow tokens
+ escrowAddress := types.GetEscrowAddress(packet.GetDestPort(), packet.GetDestChannel())
+ return k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token))
+}
+
+// sender chain is the source, mint vouchers
+
+// since SendPacket did not prefix the denomination, we must prefix denomination here
+sourcePrefix := types.GetDenomPrefix(packet.GetDestPort(), packet.GetDestChannel())
+// NOTE: sourcePrefix contains the trailing "/"
+prefixedDenom := sourcePrefix + data.Denom
+
+// construct the denomination trace from the full raw denomination
+denomTrace := types.ParseDenomTrace(prefixedDenom)
+
+// set the value to the lookup table if not stored already
+traceHash := denomTrace.Hash()
+if !k.HasDenomTrace(ctx, traceHash) {
+ k.SetDenomTrace(ctx, traceHash, denomTrace)
+}
+
+voucherDenom := denomTrace.IBCDenom()
+voucher := sdk.NewCoin(voucherDenom, sdk.NewIntFromUint64(data.Amount))
+
+// mint new tokens if the source of the transfer is the same chain
+if err := k.bankKeeper.MintCoins(
+ ctx, types.ModuleName, sdk.NewCoins(voucher),
+); err != nil {
+ return err
+}
+
+// send to receiver
+return k.bankKeeper.SendCoinsFromModuleToAccount(
+ ctx, types.ModuleName, receiver, sdk.NewCoins(voucher),
+)
+```
+
+```go
+func NewDenomTraceFromRawDenom(denom string) DenomTrace{
+ denomSplit := strings.Split(denom, "/")
+ trace := ""
+ if len(denomSplit) > 1 {
+ trace = strings.Join(denomSplit[:len(denomSplit)-1], "/")
+ }
+ return DenomTrace{
+ BaseDenom: denomSplit[len(denomSplit)-1],
+ Trace: trace,
+ }
+}
+```
+
+One final remark is that the `FungibleTokenPacketData` will remain the same, i.e with the prefixed full denomination, since the receiving chain may not be an SDK-based chain.
+
+### Coin Changes
+
+The coin denomination validation will need to be updated to reflect these changes. In particular, the denomination validation
+function will now:
+
+- Accept slash separators (`"/"`) and uppercase characters (due to the `HexBytes` format)
+- Bump the maximum character length to 128, as the hex representation used by Tendermint's
+ `HexBytes` type contains 64 characters.
+
+Additional validation logic, such as verifying the length of the hash, the may be added to the bank module in the future if the [custom base denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755) is integrated into the SDK.
+
+### Positive
+
+- Clearer separation of the source tracing behaviour of the token (transfer prefix) from the original
+ `Coin` denomination
+- Consistent validation of `Coin` fields (i.e no special characters, fixed max length)
+- Cleaner `Coin` and standard denominations for IBC
+- No additional fields to SDK `Coin`
+
+### Negative
+
+- Store each set of tracing denomination identifiers on the `ibc-transfer` module store
+- Clients will have to fetch the base denomination every time they receive a new relayed fungible token over IBC. This can be mitigated using a map/cache for already seen hashes on the client side. Other forms of mitigation, would be opening a websocket connection subscribe to incoming events.
+
+### Neutral
+
+- Slight difference with the ICS20 spec
+- Additional validation logic for IBC coins on the `ibc-transfer` module
+- Additional genesis fields
+- Slightly increases the gas usage on cross-chain transfers due to access to the store. This should
+ be inter-block cached if transfers are frequent.
+
+## References
+
+- [ICS 20 - Fungible token transfer](https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer)
+- [Custom Coin Denomination validation](https://github.com/cosmos/cosmos-sdk/pull/6755)
diff --git a/ibc/next/architecture/adr-002-go-module-versioning.mdx b/ibc/next/architecture/adr-002-go-module-versioning.mdx
new file mode 100644
index 00000000..0be09356
--- /dev/null
+++ b/ibc/next/architecture/adr-002-go-module-versioning.mdx
@@ -0,0 +1,114 @@
+---
+title: "ADR 002: Go Module Versioning"
+---
+
+## Changelog
+
+- 05-01-2022: initial draft
+
+## Status
+
+Accepted
+
+## Context
+
+The IBC module was originally developed in the Cosmos SDK and released during the Stargate release series (v0.42).
+It was subsequently migrated to its own repository, ibc-go.
+The first official release on ibc-go was v1.0.0.
+v1.0.0 was decided to be used instead of v0.1.0 primarily for the following reasons:
+
+- Maintaining compatibility with the IBC specification v1 requires stronger support/guarantees.
+- Using the major, minor, and patch numbers allows for easier communication of what breaking changes are included in a release.
+- The IBC module is being used by numerous high value projects which require stability.
+
+### Problems
+
+#### Go module version must be incremented
+
+When a Go module is released under v1.0.0, all following releases must follow Go semantic versioning.
+Thus when the go API is broken, the Go module major version **must** be incremented.
+For example, changing the go package version from `v2` to `v3` bumps the import from `github.com/cosmos/ibc-go/v2` to `github.com/cosmos/ibc-go/v3`.
+
+If the Go module version is not incremented then attempting to go get a module @v3.0.0 without the suffix results in:
+`invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v3`
+
+Version validation was added in Go 1.13. This means that in order to release a v3.0.0 git tag without a /v3 suffix on the module definition, the tag must explicitly **not** contain a go.mod file.
+Not including a go.mod in our release is not a viable option.
+
+#### Attempting to import multiple go module versions for ibc-go
+
+Attempting to import two versions of ibc-go, such as `github.com/cosmos/ibc-go/v2` and `github.com/cosmos/ibc-go/v3`, will result in multiple issues.
+
+The Cosmos SDK does global registration of error and governance proposal types.
+The errors and proposals used in ibc-go would need to now register their naming based on the go module version.
+
+The more concerning problem is that protobuf definitions will also reach a namespace collision.
+ibc-go and the Cosmos SDK in general rely heavily on using extended functions for go structs generated from protobuf definitions.
+This requires the go structs to be defined in the same package as the extended functions.
+Thus, bumping the import versioning causes the protobuf definitions to be generated in two places (in v2 and v3).
+When registering these types at compile time, the go compiler will panic.
+The generated types need to be registered against the proto codec, but there exist two definitions for the same name.
+
+The protobuf conflict policy can be overridden via the environment variable `GOLANG_PROTOBUF_REGISTRATION_CONFLICT`, but it is possible this could lead to various runtime errors or unexpected behaviour (see [here](https://github.com/protocolbuffers/protobuf-go/blob/master/reflect/protoregistry/registry.go#L46)).
+More information [here](https://developers.google.com/protocol-buffers/docs/reference/go/faq#namespace-conflict) on namespace conflicts for protobuf versioning.
+
+### Potential solutions
+
+#### Changing the protobuf definition version
+
+The protobuf definitions all have a type URL containing the protobuf version for this type.
+Changing the protobuf version would solve the namespace collision which arise from importing multiple versions of ibc-go, but it leads to new issues.
+
+In the Cosmos SDK, `Any`s are unpacked and decoded using the type URL.
+Changing the type URL thus is creating a distinctly different type.
+The same registration on the proto codec cannot be used to unpack the new type.
+For example:
+
+All Cosmos SDK messages are packed into `Any`s. If we incremented the protobuf version for our IBC messages, clients which submitted the v1 of our Cosmos SDK messages would now be rejected since the old type is not registered on the codec.
+The clients must know to submit the v2 of these messages. This pushes the burden of versioning onto relayers and wallets.
+
+A more serious problem is that the `ClientState` and `ConsensusState` are packed as `Any`s. Changing the protobuf versioning of these types would break compatibility with IBC specification v1.
+
+#### Moving protobuf definitions to their own go module
+
+The protobuf definitions could be moved to their own go module which uses 0.x versioning and will never go to 1.0.
+This prevents the Go module version from being incremented with breaking changes.
+It also requires all extended functions to live in the same Go module, disrupting the existing code structure.
+
+The version that implements this change will still be incompatible with previous versions, but future versions could be imported together without namespace collisions.
+For example, let's say this solution is implemented in v3. Then
+
+`github.com/cosmos/ibc-go/v2` cannot be imported with any other ibc-go version
+
+`github.com/cosmos/ibc-go/v3` cannot be imported with any previous ibc-go versions
+
+`github.com/cosmos/ibc-go/v4` may be imported with ibc-go versions v3+
+
+`github.com/cosmos/ibc-go/v5` may be imported with ibc-go versions v3+
+
+## Decision
+
+Supporting importing multiple versions of ibc-go requires a non-trivial amount of complexity.
+It is unclear when a user of the ibc-go code would need multiple versions of ibc-go.
+Until there is an overwhelming reason to support importing multiple versions of ibc-go:
+
+**Major releases cannot be imported simultaneously**.
+Releases should focus on keeping backwards compatibility for go code clients, within reason.
+Old functionality should be marked as deprecated and there should exist upgrade paths between major versions.
+Deprecated functionality may be removed when no clients rely on that functionality.
+How this is determined is to be decided.
+
+**Error and proposal type registration will not be changed between go module version increments**.
+This explicitly stops external clients from trying to import two major versions (potentially risking a bug due to the instability of proto name collisions override).
+
+## Consequences
+
+This only affects clients relying directly on the go code.
+
+### Positive
+
+### Negative
+
+Multiple ibc-go versions cannot be imported.
+
+### Neutral
diff --git a/ibc/next/architecture/adr-003-ics27-acknowledgement.mdx b/ibc/next/architecture/adr-003-ics27-acknowledgement.mdx
new file mode 100644
index 00000000..0865e53b
--- /dev/null
+++ b/ibc/next/architecture/adr-003-ics27-acknowledgement.mdx
@@ -0,0 +1,122 @@
+---
+title: "ADR 003: ICS-27 Acknowledgement Format"
+---
+
+## Changelog
+
+- 28-01-2022: Initial Draft
+
+## Status
+
+Accepted
+
+## Context
+
+Upon receiving an IBC packet, an IBC application can optionally return an acknowledgement.
+This acknowledgement will be hashed and written into state. Thus any changes to the information included in an acknowledgement are state machine breaking.
+
+ICS27 executes transactions on behalf of a controller chain. Information such as the message result or message error may be returned from other SDK modules outside the control of the ICS27 module.
+It might be very valuable to return message execution information inside the ICS27 acknowledgement so that controller chain interchain account auth modules can act upon this information.
+Only deterministic information returned from the message execution is allowed to be returned in the packet acknowledgement otherwise the network will halt due to a fork in the expected app hash.
+
+## Decision
+
+At the time of this writing, Tendermint includes the following information in the [ABCI.ResponseDeliverTx](https://github.com/tendermint/tendermint/blob/release/v0.34.13/types/results.go#L47-#L53):
+
+```go
+// deterministicResponseDeliverTx strips non-deterministic fields from
+// ResponseDeliverTx and returns another ResponseDeliverTx.
+func deterministicResponseDeliverTx(response *abci.ResponseDeliverTx) *abci.ResponseDeliverTx {
+ return &abci.ResponseDeliverTx{
+ Code: response.Code,
+ Data: response.Data,
+ GasWanted: response.GasWanted,
+ GasUsed: response.GasUsed,
+ }
+}
+```
+
+### Successful acknowledgements
+
+Successful acknowledgements should return information about the transaction execution.
+Given the deterministic fields in the `abci.ResponseDeliverTx`, the transaction `Data` can be used to indicate information about the transaction execution.
+The `abci.ResponseDeliverTx.Data` will be set in the ICS27 packet acknowledgement upon successful transaction execution.
+
+The format for the `abci.ResponseDeliverTx.Data` is constructed by the SDK.
+
+At the time of this writing, the next major release of the SDK will change the format for constructing the transaction response data.
+
+#### v0.45 format
+
+The current version, v0.45 constructs the transaction response as follows:
+
+```go
+proto.Marshal(&sdk.TxMsgData{
+ Data: []*sdk.MsgData{msgResponses...},
+}
+```
+
+Where `msgResponses` is a slice of `*sdk.MsgData`.
+The `MsgData.MsgType` contains the `sdk.MsgTypeURL` of the `sdk.Msg` being executed.
+The `MsgData.Data` contains the proto marshaled `MsgResponse` for the associated message executed.
+
+#### Next major version format
+
+The next major version will construct the transaction response as follows:
+
+```go
+proto.Marshal(&sdk.TxMsgData{
+ MsgResponses: []*codectypes.Any{msgResponses...},
+}
+```
+
+Where `msgResponses` is a slice of the `MsgResponse`s packed into `Any`s.
+
+#### Forwards compatible approach
+
+A forwards compatible approach was deemed infeasible.
+The `handler` provided by the `MsgServiceRouter` will only include the `*sdk.Result` and an error (if one occurred).
+In v0.45 of the SDK, the `*sdk.Result.Data` will contain the MsgResponse marshaled data.
+However, the MsgResponse is not packed and marshaled as a `*codectypes.Any`, thus making it impossible from a generalized point of view to unmarshal the bytes.
+If the bytes could be unmarshaled, then they could be packed into an `*codectypes.Any` in anticipation of the upcoming format.
+
+Intercepting the MsgResponse before it becomes marshaled requires replicating this [code](https://github.com/cosmos/cosmos-sdk/blob/dfd47f5b449f558a855da284a9a7eabbfbad435d/baseapp/msg_service_router.go#L109-#L128).
+It may not even be possible to replicate the linked code. The method handler would need to be accessed somehow.
+
+For these reasons it is deemed infeasible to attempt a forwards compatible approach.
+
+ICA auth developers can interpret which format was used when constructing the transaction response by checking if the `sdk.TxMsgData.Data` field is non-empty.
+If the `sdk.TxMsgData.Data` field is not empty then the format for v0.45 was used, otherwise ICA auth developers can assume the transaction response uses the newer format.
+
+#### Decision
+
+Replicate the transaction response format as provided by the current SDK version.
+When the SDK version changes, adjust the transaction response format to use the updated transaction response format.
+Include the transaction response bytes in the result channel acknowledgement.
+
+A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/ibc_module_test.go#L716-#L774) to fail if the `MsgResponse` is no longer included in consensus.
+
+### Error acknowledgements
+
+As indicated above, the `abci.ResponseDeliverTx.Code` is deterministic.
+Upon transaction execution errors, an error acknowledgement should be returned including the abci code.
+
+A test has been [written](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/apps/27-interchain-accounts/host/types/ack_test.go#L41-#L82) to fail if the ABCI code is no longer deterministic.
+
+## Consequences
+
+> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones.
+
+### Positive
+
+- interchain account auth modules can act upon transaction results without requiring a query module
+- transaction results align with those returned by execution of a normal SDK message.
+
+### Negative
+
+- the security assumptions of this decision rest on the inclusion of the ABCI error code and the Msg response in the ResponseDeliverTx hash created by Tendermint
+- events are non-deterministic and cannot be included in the packet acknowledgement
+
+### Neutral
+
+No neutral consequences.
diff --git a/ibc/next/architecture/adr-004-ics29-lock-fee-module.mdx b/ibc/next/architecture/adr-004-ics29-lock-fee-module.mdx
new file mode 100644
index 00000000..5b4059ae
--- /dev/null
+++ b/ibc/next/architecture/adr-004-ics29-lock-fee-module.mdx
@@ -0,0 +1,60 @@
+---
+title: "ADR 004: Lock Fee Module Upon Escrow Out Of Balance"
+---
+
+## Changelog
+
+- 03-03-2022: initial draft
+
+## Status
+
+Accepted
+
+## Context
+
+The fee module maintains an escrow account for all fees escrowed to incentivize packet relays.
+It also tracks each packet fee escrowed separately from the escrow account. This is because the escrow account only maintains a total balance. It has no reference for which coins belonged to which packet fee.
+In the presence of a severe bug, it is possible the escrow balance will become out of sync with the packet fees marked as escrowed.
+The ICS29 module should be capable of elegantly handling such a scenario.
+
+## Decision
+
+We will allow for the ICS29 module to become "locked" if the escrow balance is determined to be out of sync with the packet fees marked as escrowed.
+A "locked" fee module will not allow for packet escrows to occur nor will it distribute fees. All IBC callbacks will skip performing fee logic, similar to fee disabled channels.
+
+Manual intervention will be needed to unlock the fee module.
+
+### Sending side
+
+Special behaviour will have to be accounted for in `OnAcknowledgementPacket`. Since the counterparty will continue to send incentivized acknowledgements for fee enabled channels, the acknowledgement will still need to be unmarshalled into an incentivized acknowledgement before calling the underlying application `OnAcknowledgePacket` callback.
+
+When distributing fees, a cached context should be used. If the escrow account balance would become negative, the current state changes should be discarded and the fee module should be locked using the uncached context. This prevents fees from being partially distributed for a given packetID.
+
+### Receiving side
+
+`OnRecvPacket` should remain unaffected by the fee module becoming locked since escrow accounts only affect the sending side.
+
+## Consequences
+
+### Positive
+
+The fee module can be elegantly disabled in the presence of severe bugs.
+
+### Negative
+
+Extra logic is added to account for edge cases which are only possible in the presence of bugs.
+
+### Neutral
+
+## References
+
+Issues:
+
+- [#821](https://github.com/cosmos/ibc-go/issues/821)
+- [#860](https://github.com/cosmos/ibc-go/issues/860)
+
+PR's:
+
+- [#1031](https://github.com/cosmos/ibc-go/pull/1031)
+- [#1029](https://github.com/cosmos/ibc-go/pull/1029)
+- [#1056](https://github.com/cosmos/ibc-go/pull/1056)
diff --git a/ibc/next/architecture/adr-005-consensus-height-events.mdx b/ibc/next/architecture/adr-005-consensus-height-events.mdx
new file mode 100644
index 00000000..c07b9130
--- /dev/null
+++ b/ibc/next/architecture/adr-005-consensus-height-events.mdx
@@ -0,0 +1,94 @@
+---
+title: "ADR 005: UpdateClient Events ClientState Consensus Heights"
+---
+
+## Changelog
+
+- 25-04-2022: initial draft
+
+## Status
+
+Accepted
+
+## Context
+
+The `ibc-go` implementation leverages the [Cosmos-SDK's EventManager](https://github.com/cosmos/cosmos-sdk/blob/v0.45.4/docs/core/events.md#EventManager) to provide subscribers a method of reacting to application specific events.
+Some IBC relayers depend on the [`consensus_height`](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/events.go#L33) attribute emitted as part of `UpdateClient` events in order to run `07-tendermint` misbehaviour detection by cross-checking the details of the *Header* emitted at a given consensus height against those of the *Header* from the originating chain. This includes such details as:
+
+- The `SignedHeader` containing the commitment root.
+- The `ValidatorSet` that signed the *Header*.
+- The `TrustedHeight` seen by the client at less than or equal to the height of *Header*.
+- The last `TrustedValidatorSet` at the trusted height.
+
+Following the refactor of the `02-client` submodule and associated `ClientState` interfaces, it will now be possible for
+light client implementations to perform such actions as batch updates, inserting `N` number of `ConsensusState`s into the application state tree with a single `UpdateClient` message. This flexibility is provided in `ibc-go` by the usage of the [Protobuf `Any`](https://developers.google.com/protocol-buffers/docs/proto3#any) field contained within the [`UpdateClient`](https://github.com/cosmos/ibc-go/blob/v3.0.0/proto/ibc/core/client/v1/tx.proto#L44) message.
+For example, a batched client update message serialized as a Protobuf `Any` type for the `07-tendermint` lightclient implementation could be defined as follows:
+
+```protobuf
+message BatchedHeaders {
+ repeated Header headers = 1;
+}
+```
+
+To complement this flexibility, the `UpdateClient` handler will now support the submission of [client misbehaviour](https://github.com/cosmos/ibc/tree/master/spec/core/ics-002-client-semantics#misbehaviour) by consolidating the `Header` and `Misbehaviour` interfaces into a single `ClientMessage` interface type:
+
+```go
+// ClientMessage is an interface used to update an IBC client.
+// The update may be done by a single header, a batch of headers, misbehaviour, or any type which when verified produces
+// a change to state of the IBC client
+type ClientMessage interface {
+ proto.Message
+
+ ClientType() string
+ ValidateBasic() error
+}
+```
+
+To support this functionality the `GetHeight()` method has been omitted from the new `ClientMessage` interface.
+Emission of standardised events from the `02-client` submodule now becomes problematic and is two-fold:
+
+1. The `02-client` submodule previously depended upon the `GetHeight()` method of `Header` types in order to [retrieve the updated consensus height](https://github.com/cosmos/ibc-go/blob/v3.0.0/modules/core/02-client/keeper/client.go#L90).
+2. Emitting a single `consensus_height` event attribute is not sufficient in the case of a batched client update containing multiple *Headers*.
+
+## Decision
+
+The following decisions have been made in order to provide flexibility to consumers of `UpdateClient` events in a non-breaking fashion:
+
+1. Return a list of updated consensus heights `[]exported.Height` from the new `UpdateState` method of the `ClientState` interface.
+
+```go
+// UpdateState updates and stores as necessary any associated information for an IBC client, such as the ClientState and corresponding ConsensusState.
+// Upon successful update, a list of consensus heights is returned. It assumes the ClientMessage has already been verified.
+UpdateState(sdk.Context, codec.BinaryCodec, sdk.KVStore, ClientMessage) []Height
+```
+
+2. Maintain the `consensus_height` event attribute emitted from the `02-client` update handler, but mark as deprecated for future removal. For example, with tendermint lightclients this will simply be `consensusHeights[0]` following a successful update using a single *Header*.
+
+3. Add an additional `consensus_heights` event attribute, containing a comma separated list of updated heights. This provides flexibility for emitting a single consensus height or multiple consensus heights in the example use-case of batched header updates.
+
+## Consequences
+
+### Positive
+
+- Subscribers of IBC core events can act upon `UpdateClient` events containing one or more consensus heights.
+- Deprecation of the existing `consensus_height` attribute allows consumers to continue to process `UpdateClient` events as normal, with a path to upgrade to using the `consensus_heights` attribute moving forward.
+
+### Negative
+
+- Consumers of IBC core `UpdateClient` events are forced to make future code changes.
+
+### Neutral
+
+## References
+
+Discussions:
+
+- [#1208](https://github.com/cosmos/ibc-go/pull/1208#discussion_r839691927)
+
+Issues:
+
+- [#594](https://github.com/cosmos/ibc-go/issues/594)
+
+PRs:
+
+- [#1285](https://github.com/cosmos/ibc-go/pull/1285)
diff --git a/ibc/next/architecture/adr-006-02-client-refactor.mdx b/ibc/next/architecture/adr-006-02-client-refactor.mdx
new file mode 100644
index 00000000..f165331c
--- /dev/null
+++ b/ibc/next/architecture/adr-006-02-client-refactor.mdx
@@ -0,0 +1,205 @@
+---
+title: "ADR 006: 02-Client Refactor"
+---
+
+## Changelog
+
+- 01-08-2022: Initial Draft
+
+## Status
+
+Accepted and applied in v7 of ibc-go
+
+## Context
+
+During the initial development of the 02-client submodule, each light client supported (06-solomachine, 07-tendermint, 09-localhost) was referenced through hardcoding.
+Here is an example of the [code](https://github.com/cosmos/cosmos-sdk/commit/b93300288e3a04faef9c0774b75c13b24450ba1c#diff-c5f6b956947375f28d611f18d0e670cf28f8f305300a89c5a9b239b0eeec5064R83) that existed in the 02-client submodule:
+
+```go
+func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) (exported.ClientState, error) {
+ ...
+
+ switch clientType {
+ case exported.Tendermint:
+ clientState, consensusState, err = tendermint.CheckValidityAndUpdateState(
+ clientState, header, ctx.BlockTime(),
+ )
+ case exported.Localhost:
+ // override client state and update the block height
+ clientState = localhosttypes.NewClientState(
+ ctx.ChainID(), // use the chain ID from context since the client is from the running chain (i.e self).
+ ctx.BlockHeight(),
+ )
+ default:
+ err = types.ErrInvalidClientType
+ }
+```
+
+To add additional light clients, code would need to be added directly to the 02-client submodule.
+Evidently, this would likely become problematic as IBC scaled to many chains using consensus mechanisms beyond the initial supported light clients.
+Issue [#6064](https://github.com/cosmos/cosmos-sdk/issues/6064) on the SDK addressed this problem by creating a more modular 02-client submodule.
+The 02-client submodule would now interact with each light client via an interface.
+While, this change was positive in development, increasing the flexibility and adoptability of IBC, it also opened the door to new problems.
+
+The difficulty of generalizing light clients became apparent once changes to those light clients were required.
+Each light client represents a different consensus algorithm which may contain a host of complexity and nuances.
+Here are some examples of issues which arose for light clients that are not applicable to all the light clients supported (06-solomachine, 07-tendermint, 09-localhost):
+
+### Tendermint non-zero height upgrades
+
+Before the launch of IBC, it was determined that the golang implementation of [tendermint](https://github.com/tendermint/tendermint) would not be capable of supporting non-zero height upgrades.
+This implies that any upgrade would require changing of the chain ID and resetting the height to 0.
+A chain is uniquely identified by its chain-id and validator set.
+Two different chain ID's can be viewed as different chains and thus a normal update produced by a validator set cannot change the chain ID.
+To work around the lack of support for non-zero height upgrades, an abstract height type was created along with an upgrade mechanism.
+This type would indicate the revision number (the number of times the chain ID has been changed) and revision height (the current height of the blockchain).
+
+Refs:
+
+- Issue [#439](https://github.com/cosmos/ibc/issues/439) on IBC specification repository.
+- Specification changes in [#447](https://github.com/cosmos/ibc/pull/447)
+- Implementation changes for the abstract height type, [SDK#7211](https://github.com/cosmos/cosmos-sdk/pull/7211)
+
+### Tendermint requires misbehaviour detection during updates
+
+The initial release of the IBC module and the 07-tendermint light client implementation did not support misbehaviour detection during update nor did it prevent overwriting of previous updates.
+Despite the fact that we designed the `ClientState` interface and developed the 07-tendermint client, we failed to detect even a duplicate update that constituted misbehaviour and thus should freeze the client.
+This was fixed in PR [#141](https://github.com/cosmos/ibc-go/pull/141) which required light client implementations to be aware that they must handle duplicate updates and misbehaviour detection.
+Misbehaviour detection during updates is not applicable to the solomachine nor localhost.
+It is also not obvious that `CheckHeaderAndUpdateState` should be performing this functionality.
+
+### Localhost requires access to the entire client store
+
+The localhost has been broken since the initial version of the IBC module.
+The localhost tried to be developed underneath the 02-client interfaces without special exception, but this proved to be impossible.
+The issues were outlined in [#27](https://github.com/cosmos/ibc-go/issues/27) and further discussed in the attempted ADR in [#75](https://github.com/cosmos/ibc-go/pull/75).
+Unlike all other clients, the localhost requires access to the entire IBC store and not just the prefixed client store.
+
+### Solomachine doesn't set consensus states
+
+The 06-solomachine does not set the consensus states within the prefixed client store.
+It has a single consensus state that is stored within the client state.
+This causes setting of the consensus state at the 02-client level to use unnecessary storage.
+It also causes timeouts to fail with solo machines.
+Previously, the timeout logic within IBC would obtain the consensus state at the height a timeout is being proved.
+This is problematic for the solo machine as no consensus state is set.
+See issue [#562](https://github.com/cosmos/ibc/issues/562) on the IBC specification repo.
+
+### New clients may want to do batch updates
+
+New light clients may not function in a similar fashion to 06-solomachine and 07-tendermint.
+They may require setting many consensus states in a single update.
+As @seunlanlege [states](https://github.com/cosmos/ibc-go/issues/284#issuecomment-1005583679):
+
+> I'm in support of these changes for 2 reasons:
+>
+> - This would allow light clients to handle batch header updates in CheckHeaderAndUpdateState, for the special case of 11-beefy proving the finality for a batch of headers is much more space and time efficient than the space/time complexity of proving each individual headers in that batch, combined.
+>
+> - This also allows for a single light client instance of 11-beefy be used to prove finality for every parachain connected to the relay chain (Polkadot/Kusama). We achieve this by setting the appropriate ConsensusState for individual parachain headers in CheckHeaderAndUpdateState
+
+## Decision
+
+### Require light clients to set client and consensus states
+
+The IBC specification states:
+
+> If the provided header was valid, the client MUST also mutate internal state to store now-finalised consensus roots and update any necessary signature authority tracking (e.g. changes to the validator set) for future calls to the validity predicate.
+
+The initial version of the IBC go SDK based module did not fulfill this requirement.
+Instead, the 02-client submodule required each light client to return the client and consensus state which should be updated in the client prefixed store.
+This decision lead to the issues "Solomachine doesn't set consensus states" and "New clients may want to do batch updates".
+
+Each light client should be required to set its own client and consensus states on any update necessary.
+The go implementation should be changed to match the specification requirements.
+This will allow more flexibility for light clients to manage their own internal storage and do batch updates.
+
+### Merge `Header`/`Misbehaviour` interface and rename to `ClientMessage`
+
+Remove `GetHeight()` from the header interface (as light clients now set the client/consensus states).
+This results in the `Header`/`Misbehaviour` interfaces being the same.
+To reduce complexity of the codebase, the `Header`/`Misbehaviour` interfaces should be merged into `ClientMessage`.
+`ClientMessage` will provide the client with some authenticated information which may result in regular updates, misbehaviour detection, batch updates, or other custom functionality a light client requires.
+
+### Split `CheckHeaderAndUpdateState` into 4 functions
+
+See [#668](https://github.com/cosmos/ibc-go/issues/668).
+
+Split `CheckHeaderAndUpdateState` into 4 functions:
+
+- `VerifyClientMessage`
+- `CheckForMisbehaviour`
+- `UpdateStateOnMisbehaviour`
+- `UpdateState`
+
+`VerifyClientMessage` checks the that the structure of a `ClientMessage` is correct and that all authentication data provided is valid.
+
+`CheckForMisbehaviour` checks to see if a `ClientMessage` is evidence of misbehaviour.
+
+`UpdateStateOnMisbehaviour` freezes the client and updates its state accordingly.
+
+`UpdateState` performs a regular update or a no-op on duplicate updates.
+
+The code roughly looks like:
+
+```go
+func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, header exported.Header) error {
+ ...
+
+ if err := clientState.VerifyClientMessage(clientMessage); err != nil {
+ return err
+ }
+
+ foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage)
+ if foundMisbehaviour {
+ clientState.UpdateStateOnMisbehaviour(header)
+ // emit misbehaviour event
+ return
+ }
+
+ clientState.UpdateState(clientMessage) // expects no-op on duplicate header
+ // emit update event
+ return
+}
+```
+
+### Add `GetTimestampAtHeight` to the client state interface
+
+By adding `GetTimestampAtHeight` to the ClientState interface, we allow light clients which do non-traditional consensus state/timestamp storage to process timeouts correctly.
+This fixes the issues outlined for the solo machine client.
+
+### Add generic verification functions
+
+As the complexity and the functionality grows, new verification functions will be required for additional paths.
+This was explained in [#684](https://github.com/cosmos/ibc/issues/684) on the specification repo.
+These generic verification functions would be immediately useful for the new paths added in connection/channel upgradability as well as for custom paths defined by IBC applications such as Interchain Queries.
+The old verification functions (`VerifyClientState`, `VerifyConnection`, etc) should be removed in favor of the generic verification functions.
+
+## Consequences
+
+### Positive
+
+- Flexibility for light client implementations
+- Well defined interfaces and their required functionality
+- Generic verification functions
+- Applies changes necessary for future client/connection/channel upgrabability features
+- Timeout processing for solo machines
+- Reduced code complexity
+
+### Negative
+
+- The refactor touches on sensitive areas of the ibc-go codebase
+- Changing of established naming (`Header`/`Misbehaviour` to `ClientMessage`)
+
+### Neutral
+
+No notable consequences
+
+## References
+
+Issues:
+
+- [#284](https://github.com/cosmos/ibc-go/issues/284)
+
+PRs:
+
+- [#1871](https://github.com/cosmos/ibc-go/pull/1871)
diff --git a/ibc/next/architecture/adr-007-solomachine-signbytes.mdx b/ibc/next/architecture/adr-007-solomachine-signbytes.mdx
new file mode 100644
index 00000000..e1872e24
--- /dev/null
+++ b/ibc/next/architecture/adr-007-solomachine-signbytes.mdx
@@ -0,0 +1,54 @@
+---
+title: "ADR 007: Solo Machine Sign Bytes"
+---
+
+## Changelog
+
+- 02-08-2022: Initial draft
+
+## Status
+
+Accepted, applied in v7
+
+## Context
+
+The `06-solomachine` implementation up until ibc-go v7 constructed sign bytes using a `DataType` which described what type of data was being signed.
+This design decision arose from a misunderstanding of the security implications.
+It was noted that the proto definitions do not [provide uniqueness](https://github.com/cosmos/cosmos-sdk/pull/7237#discussion_r484264573) which is a necessity for ensuring two signatures over different data types can never be the same.
+What was missed is that the uniqueness is not provided by the proto definition, but by the usage of the proto definition.
+The path provided by core IBC will be unique and is already encoded into the signature data.
+Thus two different paths with the same data values will encode differently which provides signature uniqueness.
+
+Furthermore, the current construction does not support the proposed changes in the spec repo to support [Generic Verification functions](https://github.com/cosmos/ibc/issues/684).
+This is because in order to verify a new path, a new `DataType` must be added for that path.
+
+## Decision
+
+Remove `DataType` and change the `DataType` in the `SignBytes` and `SignatureAndData` to be `Path`.
+The new `Path` field should be bytes.
+Remove all `...Data` proto definitions except for `HeaderData`
+These `...Data` definitions were created previously for each `DataType`.
+The proto version of the solo machine proto definitions should be bumped to `v3`.
+
+This removes an extra layer of complexity from signature construction and allows for support of generic verification.
+
+## Consequences
+
+### Positive
+
+- Simplification of solo machine signature construction
+- Support for generic verification
+
+### Negative
+
+- Breaks existing signature construction in a non-backwards compatible way
+- Solo machines must update to handle the new format
+- Migration required for solo machine client and consensus states
+
+### Neutral
+
+No notable consequences
+
+## References
+
+- [#1141](https://github.com/cosmos/ibc-go/issues/1141)
diff --git a/ibc/next/architecture/adr-008-app-caller-cbs.mdx b/ibc/next/architecture/adr-008-app-caller-cbs.mdx
new file mode 100644
index 00000000..f75967b3
--- /dev/null
+++ b/ibc/next/architecture/adr-008-app-caller-cbs.mdx
@@ -0,0 +1,571 @@
+---
+title: "ADR 008: Callback to IBC Actors"
+---
+
+## Changelog
+
+- 10-08-2022: Initial Draft
+- 22-03-2023: Merged
+- 13-09-2023: Updated with decisions made in implementation
+- 24-02-2025: RecvPacket callback error now returns error acknowledgement
+
+## Status
+
+Accepted, middleware implemented
+
+## Context
+
+IBC was designed with callbacks between core IBC and IBC applications. IBC apps would send a packet to core IBC. When the result of the packet lifecycle eventually resolved into either an acknowledgement or a timeout, core IBC called a callback on the IBC application so that the IBC application could take action on the basis of the result (e.g. unescrow tokens for ICS-20).
+
+This setup worked well for off-chain users interacting with IBC applications.
+
+We are now seeing the desire for secondary applications (e.g. smart contracts, modules) to call into IBC apps as part of their state machine logic and then do some actions on the basis of the packet result. Or to receive a packet from IBC and do some logic upon receipt.
+
+Example Usecases:
+
+- Send an ICS-20 packet, and if it is successful, then send an ICA-packet to swap tokens on LP and return funds to sender
+- Execute some logic upon receipt of token transfer to a smart contract address
+
+This requires a second layer of callbacks. The IBC application already gets the result of the packet from core IBC, but currently there is no standardized way to pass this information on to an actor module/smart contract.
+
+## Definitions
+
+- Actor: an actor is an on-chain module (this may be a hardcoded module in the chain binary or a smart contract) that wishes to execute custom logic whenever IBC receives a packet flow that it has either sent or received. It **must** be addressable by a string value.
+
+## Decision
+
+Create a middleware that can interface between IBC applications and smart contract VMs. The IBC applications and smart contract VMs will implement respective interfaces that will then be composed together by the callback middleware to allow a smart contract of any compatible VM to interact programmatically with an IBC application.
+
+## Data structures
+
+The `CallbackPacketData` struct will get constructed from custom callback data in the application packet. The `CallbackAddress` is the IBC Actor address on which the callback should be called on. The `SenderAddress` is also provided to optionally allow a VM to ensure that the sender is the same as the callback address.
+
+The struct also defines a `CommitGasLimit` which is the maximum gas a callback is allowed to use. If the callback exceeds this limit, the callback will panic and the tx will commit without the callback's state changes.
+
+The `ExecutionGasLimit` is the practical limit of the tx execution that is set in the context gas meter. It is the minimum of the `CommitGasLimit` and the gas left in the context gas meter which is determined by the relayer's choice of tx gas limit. If `ExecutionGasLimit < CommitGasLimit`, then an out-of-gas error will revert the entire transaction without committing anything, allowing for a different relayer to retry with a larger tx gas limit.
+
+Any middleware targeting this interface for callback handling should define a global limit that caps the gas that a callback is allowed to take (especially on AcknowledgePacket and TimeoutPacket) so that a custom callback does not prevent the packet lifecycle from completing. However, since this is a global cap it is likely to be very large. Thus, users may specify a smaller limit to cap the amount of fees a relayer must pay in order to complete the packet lifecycle on the user's behalf.
+
+```go
+// Implemented by any packet data type that wants to support PacketActor callbacks
+// PacketActor's will be unable to act on any packet data type that does not implement
+// this interface.
+type CallbackPacketData struct {
+ CallbackAddress: string
+ ExecutionGasLimit: uint64
+ SenderAddress: string
+ CommitGasLimit: uint64
+}
+```
+
+IBC Apps or middleware can then call the IBCActor callbacks like so in their own callbacks:
+
+### Callback Middleware
+
+The CallbackMiddleware wraps an underlying IBC application along with a contractKeeper that delegates the callback to a virtual machine. This allows the Callback middleware to interface any compatible IBC application with any compatible VM (e.g. EVM, WASM) so long as the application implements the `CallbacksCompatibleModule` interface and the VM implements the `ContractKeeper` interface.
+
+```go
+// IBCMiddleware implements the ICS26 callbacks for the ibc-callbacks middleware given
+// the underlying application.
+type IBCMiddleware struct {
+ app types.CallbacksCompatibleModule
+ ics4Wrapper porttypes.ICS4Wrapper
+
+ contractKeeper types.ContractKeeper
+
+ // maxCallbackGas defines the maximum amount of gas that a callback actor can ask the
+ // relayer to pay for. If a callback fails due to insufficient gas, the entire tx
+ // is reverted if the relayer hadn't provided the minimum(userDefinedGas, maxCallbackGas).
+ // If the actor hasn't defined a gas limit, then it is assumed to be the maxCallbackGas.
+ maxCallbackGas uint64
+}
+```
+
+### Callback-Compatible IBC Application
+
+The `CallbacksCompatibleModule` extends `porttypes.IBCModule` to include an `UnmarshalPacketData` function that allows the middleware to request that the underlying app unmarshal the packet data. This will then allow the middleware to retrieve the callback specific data from an arbitrary set of IBC application packets.
+
+```go
+// CallbacksCompatibleModule is an interface that combines the IBCModule and PacketDataUnmarshaler
+// interfaces to assert that the underlying application supports both.
+type CallbacksCompatibleModule interface {
+ porttypes.IBCModule
+ porttypes.PacketDataUnmarshaler
+}
+
+// PacketDataUnmarshaler defines an optional interface which allows a middleware to
+// request the packet data to be unmarshaled by the base application.
+type PacketDataUnmarshaler interface {
+ // UnmarshalPacketData unmarshals the packet data into a concrete type
+ // ctx, portID, channelID are provided as arguments, so that (if needed)
+ // the packet data can be unmarshaled based on the channel version.
+ // the version of the underlying app is also returned.
+ UnmarshalPacketData(ctx sdk.Context, portID, channelID string, bz []byte) (interface{}, string, error)
+}
+```
+
+The application's packet data must additionally implement the following interfaces:
+
+```go
+// PacketData defines an optional interface which an application's packet data structure may implement.
+type PacketData interface {
+ // GetPacketSender returns the sender address of the packet data.
+ // If the packet sender is unknown or undefined, an empty string should be returned.
+ GetPacketSender(sourcePortID string) string
+}
+
+// PacketDataProvider defines an optional interfaces for retrieving custom packet data stored on behalf of another application.
+// An existing problem in the IBC middleware design is the inability for a middleware to define its own packet data type and insert packet sender provided information.
+// A short term solution was introduced into several application's packet data to utilize a memo field to carry this information on behalf of another application.
+// This interfaces standardizes that behaviour. Upon realization of the ability for middleware's to define their own packet data types, this interface will be deprecated and removed with time.
+type PacketDataProvider interface {
+ // GetCustomPacketData returns the packet data held on behalf of another application.
+ // The name the information is stored under should be provided as the key.
+ // If no custom packet data exists for the key, nil should be returned.
+ GetCustomPacketData(key string) interface{}
+}
+```
+
+The callback data can be embedded in an application packet by providing custom packet data for source and destination callback in the custom packet data under the appropriate key.
+
+```jsonc
+// Custom Packet data embedded as a JSON object in the packet data
+
+// src callback custom data
+{
+ "src_callback": {
+ "address": "callbackAddressString",
+ // optional
+ "gas_limit": "userDefinedGasLimitString",
+ }
+}
+
+// dest callback custom data
+{
+ "dest_callback": {
+ "address": "callbackAddressString",
+ // optional
+ "gas_limit": "userDefinedGasLimitString",
+ }
+}
+
+// src and dest callback custom data embedded together
+{
+ "src_callback": {
+ "address": "callbackAddressString",
+ // optional
+ "gas_limit": "userDefinedGasLimitString",
+ },
+ "dest_callback": {
+ "address": "callbackAddressString",
+ // optional
+ "gas_limit": "userDefinedGasLimitString",
+ }
+}
+```
+
+## ContractKeeper
+
+The `ContractKeeper` interface must be implemented by any VM that wants to support IBC callbacks. This allows for separation of concerns
+between the middleware which is handling logic intended for all VMs (e.g. setting gas meter, extracting callback data, emitting events),
+while the ContractKeeper can handle the specific details of calling into the VM in question.
+
+The `ContractKeeper` **may** impose additional checks such as ensuring that the contract address is the same as the packet sender in source callbacks.
+It may also disable certain callback methods by simply performing a no-op.
+
+```go
+// ContractKeeper defines the entry points exposed to the VM module which invokes a smart contract
+type ContractKeeper interface {
+ // IBCSendPacketCallback is called in the source chain when a PacketSend is executed. The
+ // packetSenderAddress is determined by the underlying module, and may be empty if the sender is
+ // unknown or undefined. The contract is expected to handle the callback within the user defined
+ // gas limit, and handle any errors, or panics gracefully.
+ // This entry point is called with a cached context. If an error is returned, then the changes in
+ // this context will not be persisted, and the error will be propagated to the underlying IBC
+ // application, resulting in a packet send failure.
+ //
+ // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
+ // validation on the origin of a given packet. It is recommended to perform the same validation
+ // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
+ // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
+ //
+ // The version provided is the base application version for the given packet send. This allows
+ // contracts to determine how to unmarshal the packetData.
+ IBCSendPacketCallback(
+ cachedCtx sdk.Context,
+ sourcePort string,
+ sourceChannel string,
+ timeoutHeight clienttypes.Height,
+ timeoutTimestamp uint64,
+ packetData []byte,
+ contractAddress,
+ packetSenderAddress string,
+ version string,
+ ) error
+ // IBCOnAcknowledgementPacketCallback is called in the source chain when a packet acknowledgement
+ // is received. The packetSenderAddress is determined by the underlying module, and may be empty if
+ // the sender is unknown or undefined. The contract is expected to handle the callback within the
+ // user defined gas limit, and handle any errors, or panics gracefully.
+ // This entry point is called with a cached context. If an error is returned, then the changes in
+ // this context will not be persisted, but the packet lifecycle will not be blocked.
+ //
+ // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
+ // validation on the origin of a given packet. It is recommended to perform the same validation
+ // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
+ // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
+ //
+ // The version provided is the base application version for the given packet send. This allows
+ // contracts to determine how to unmarshal the packetData.
+ IBCOnAcknowledgementPacketCallback(
+ cachedCtx sdk.Context,
+ packet channeltypes.Packet,
+ acknowledgement []byte,
+ relayer sdk.AccAddress,
+ contractAddress,
+ packetSenderAddress string,
+ version string,
+ ) error
+ // IBCOnTimeoutPacketCallback is called in the source chain when a packet is not received before
+ // the timeout height. The packetSenderAddress is determined by the underlying module, and may be
+ // empty if the sender is unknown or undefined. The contract is expected to handle the callback
+ // within the user defined gas limit, and handle any error, out of gas, or panics gracefully.
+ // This entry point is called with a cached context. If an error is returned, then the changes in
+ // this context will not be persisted, but the packet lifecycle will not be blocked.
+ //
+ // Implementations are provided with the packetSenderAddress and MAY choose to use this to perform
+ // validation on the origin of a given packet. It is recommended to perform the same validation
+ // on all source chain callbacks (SendPacket, AcknowledgementPacket, TimeoutPacket). This
+ // defensively guards against exploits due to incorrectly wired SendPacket ordering in IBC stacks.
+ //
+ // The version provided is the base application version for the given packet send. This allows
+ // contracts to determine how to unmarshal the packetData.
+ IBCOnTimeoutPacketCallback(
+ cachedCtx sdk.Context,
+ packet channeltypes.Packet,
+ relayer sdk.AccAddress,
+ contractAddress,
+ packetSenderAddress string,
+ version string,
+ ) error
+ // IBCReceivePacketCallback is called in the destination chain when a packet acknowledgement is written.
+ // The contract is expected to handle the callback within the user defined gas limit.
+ // This entry point is called with a cached context. If an error is returned, then the error
+ // will be written as an error acknowledgement. This will cause the context changes made by the contract
+ // to be reverted along with any state changes made by the underlying application.
+ // The error acknowledgement will then be relayed to the sending application which can perform
+ // its error acknowledgement logic (e.g. refunding tokens back to user)
+ //
+ // The version provided is the base application version for the given packet send. This allows
+ // contracts to determine how to unmarshal the packetData.
+ IBCReceivePacketCallback(
+ cachedCtx sdk.Context,
+ packet ibcexported.PacketI,
+ ack ibcexported.Acknowledgement,
+ contractAddress string,
+ version string,
+ ) error
+}
+```
+
+### PacketCallbacks
+
+The packet callbacks implemented in the middleware will first call the underlying application and then route to the IBC actor callback in the post-processing step.
+It will extract the callback data from the application packet and set the callback gas meter depending on the global limit, the user limit, and the gas left in the transaction gas meter.
+The callback will then be routed through the callback keeper which will either panic or return a result (success or failure). In the event of a (non-oog) panic or an error, the callback state changes
+are discarded and the transaction is committed.
+
+If the relayer-defined gas limit is exceeded before the user-defined gas limit or global callback gas limit is exceeded, then the entire transaction is reverted to allow for resubmission. If the chain-defined or user-defined gas limit is reached,
+the callback state changes are reverted and the transaction is committed.
+
+For the `SendPacket` callback, we will revert the entire transaction on any kind of error or panic. This is because the packet lifecycle has not yet started, so we can revert completely to avoid starting the packet lifecycle if the callback is not successful.
+
+```go
+// SendPacket implements source callbacks for sending packets.
+// It defers to the underlying application and then calls the contract callback.
+// If the contract callback returns an error, panics, or runs out of gas, then
+// the packet send is rejected.
+func (im IBCMiddleware) SendPacket(
+ ctx sdk.Context,
+ chanCap *capabilitytypes.Capability,
+ sourcePort string,
+ sourceChannel string,
+ timeoutHeight clienttypes.Height,
+ timeoutTimestamp uint64,
+ data []byte,
+) (uint64, error) {
+ // run underlying app logic first
+ // IBCActor logic will postprocess
+ seq, err := im.ics4Wrapper.SendPacket(ctx, chanCap, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data)
+ if err != nil {
+ return 0, err
+ }
+
+ // use underlying app to get source callback information from packet data
+ callbackData, err := types.GetSourceCallbackData(im.app, data, sourcePort, ctx.GasMeter().GasRemaining(), im.maxCallbackGas)
+ // SendPacket is not blocked if the packet does not opt-in to callbacks
+ if err != nil {
+ return seq, nil
+ }
+
+ callbackExecutor := func(cachedCtx sdk.Context) error {
+ return im.contractKeeper.IBCSendPacketCallback(
+ cachedCtx, sourcePort, sourceChannel, timeoutHeight, timeoutTimestamp, data, callbackData.CallbackAddress, callbackData.SenderAddress,
+ )
+ }
+
+ err = im.processCallback(ctx, types.CallbackTypeSendPacket, callbackData, callbackExecutor)
+ // contract keeper is allowed to reject the packet send.
+ if err != nil {
+ return 0, err
+ }
+
+ types.EmitCallbackEvent(ctx, sourcePort, sourceChannel, seq, types.CallbackTypeSendPacket, callbackData, nil)
+ return seq, nil
+}
+
+// WriteAcknowledgement implements the ReceivePacket destination callbacks for the ibc-callbacks middleware
+// during asynchronous packet acknowledgement.
+// It defers to the underlying application and then calls the contract callback.
+// If the contract callback runs out of gas and may be retried with a higher gas limit then the state changes are
+// reverted via a panic.
+func (im IBCMiddleware) WriteAcknowledgement(
+ ctx sdk.Context,
+ chanCap *capabilitytypes.Capability,
+ packet ibcexported.PacketI,
+ ack ibcexported.Acknowledgement,
+) error {
+ // run underlying app logic first
+ // IBCActor logic will postprocess
+ err := im.ics4Wrapper.WriteAcknowledgement(ctx, chanCap, packet, ack)
+ if err != nil {
+ return err
+ }
+
+ // use underlying app to get destination callback information from packet data
+ callbackData, err := types.GetDestCallbackData(
+ im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas,
+ )
+ // WriteAcknowledgement is not blocked if the packet does not opt-in to callbacks
+ if err != nil {
+ return nil
+ }
+
+ callbackExecutor := func(cachedCtx sdk.Context) error {
+ return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress)
+ }
+
+ // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
+ err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
+ // emit events
+ types.EmitCallbackEvent(
+ ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
+ types.CallbackTypeAcknowledgementPacket, callbackData, err,
+ )
+
+ return nil
+}
+
+// Call the IBCActor recvPacket callback after processing the packet
+// if the recvPacket callback exists. If the callback returns an error
+// then return an error ack to revert all packet data processing.
+func (im IBCMiddleware) OnRecvPacket(
+ ctx sdk.Context,
+ packet channeltypes.Packet,
+ relayer sdk.AccAddress,
+) (ack exported.Acknowledgement) {
+ // run underlying app logic first
+ // IBCActor logic will postprocess
+ ack := im.app.OnRecvPacket(ctx, packet, relayer)
+ // if ack is nil (asynchronous acknowledgements), then the callback will be handled in WriteAcknowledgement
+ // if ack is not successful, all state changes are reverted. If a packet cannot be received, then there is
+ // no need to execute a callback on the receiving chain.
+ if ack == nil || !ack.Success() {
+ return ack
+ }
+
+ // use underlying app to get destination callback information from packet data
+ callbackData, err := types.GetDestCallbackData(
+ im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas,
+ )
+ // OnRecvPacket is not blocked if the packet does not opt-in to callbacks
+ if err != nil {
+ return ack
+ }
+
+ callbackExecutor := func(cachedCtx sdk.Context) error {
+ return im.contractKeeper.IBCReceivePacketCallback(cachedCtx, packet, ack, callbackData.CallbackAddress)
+ }
+
+ // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
+ err = im.processCallback(ctx, types.CallbackTypeReceivePacket, callbackData, callbackExecutor)
+ types.EmitCallbackEvent(
+ ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(),
+ types.CallbackTypeReceivePacket, callbackData, err,
+ )
+ if err != nil {
+ return channeltypes.NewErrorAcknowledgement(err)
+ }
+
+ return ack
+}
+
+// Call the IBCActor acknowledgementPacket callback after processing the packet
+// if the ackPacket callback exists and returns an error
+// DO NOT return the error upstream. The acknowledgement must complete for the packet
+// lifecycle to end, so the custom callback cannot block completion.
+// Instead we emit error events and set the error in state
+// so that users and on-chain logic can handle this appropriately
+func (im IBCModule) OnAcknowledgementPacket(
+ ctx sdk.Context,
+ packet channeltypes.Packet,
+ acknowledgement []byte,
+ relayer sdk.AccAddress,
+) error {
+ // we first call the underlying app to handle the acknowledgement
+ // IBCActor logic will postprocess
+ err := im.app.OnAcknowledgementPacket(ctx, packet, acknowledgement, relayer)
+ if err != nil {
+ return err
+ }
+
+ // use underlying app to get source callback information from packet data
+ callbackData, err := types.GetSourceCallbackData(
+ im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas,
+ )
+ // OnAcknowledgementPacket is not blocked if the packet does not opt-in to callbacks
+ if err != nil {
+ return nil
+ }
+
+ callbackExecutor := func(cachedCtx sdk.Context) error {
+ return im.contractKeeper.IBCOnAcknowledgementPacketCallback(
+ cachedCtx, packet, acknowledgement, relayer, callbackData.CallbackAddress, callbackData.SenderAddress,
+ )
+ }
+
+ // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
+ err = im.processCallback(ctx, types.CallbackTypeAcknowledgementPacket, callbackData, callbackExecutor)
+ types.EmitCallbackEvent(
+ ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
+ types.CallbackTypeAcknowledgementPacket, callbackData, err,
+ )
+
+ return nil
+}
+
+// Call the IBCActor timeoutPacket callback after processing the packet
+// if the timeoutPacket callback exists and returns an error
+// DO NOT return the error upstream. The timeout must complete for the packet
+// lifecycle to end, so the custom callback cannot block completion.
+// Instead we emit error events and set the error in state
+// so that users and on-chain logic can handle this appropriately
+func (im IBCModule) OnTimeoutPacket(
+ ctx sdk.Context,
+ packet channeltypes.Packet,
+ relayer sdk.AccAddress,
+) error {
+ // application-specific onTimeoutPacket logic
+ err := im.app.OnTimeoutPacket(ctx, packet, relayer)
+ if err != nil {
+ return err
+ }
+
+ // use underlying app to get source callback information from packet data
+ callbackData, err := types.GetSourceCallbackData(
+ im.app, packet.GetData(), packet.GetSourcePort(), ctx.GasMeter().GasRemaining(), im.maxCallbackGas,
+ )
+ // OnTimeoutPacket is not blocked if the packet does not opt-in to callbacks
+ if err != nil {
+ return nil
+ }
+
+ callbackExecutor := func(cachedCtx sdk.Context) error {
+ return im.contractKeeper.IBCOnTimeoutPacketCallback(cachedCtx, packet, relayer, callbackData.CallbackAddress, callbackData.SenderAddress)
+ }
+
+ // callback execution errors are not allowed to block the packet lifecycle, they are only used in event emissions
+ err = im.processCallback(ctx, types.CallbackTypeTimeoutPacket, callbackData, callbackExecutor)
+ types.EmitCallbackEvent(
+ ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence(),
+ types.CallbackTypeTimeoutPacket, callbackData, err,
+ )
+
+ return nil
+}
+
+// processCallback executes the callbackExecutor and reverts contract changes if the callbackExecutor fails.
+//
+// Error Precedence and Returns:
+// - oogErr: Takes the highest precedence. If the callback runs out of gas, an error wrapped with types.ErrCallbackOutOfGas is returned.
+// - panicErr: Takes the second-highest precedence. If a panic occurs and it is not propagated, an error wrapped with types.ErrCallbackPanic is returned.
+// - callbackErr: If the callbackExecutor returns an error, it is returned as-is.
+//
+// panics if
+// - the contractExecutor panics for any reason, and the callbackType is SendPacket, or
+// - the contractExecutor runs out of gas and the relayer has not reserved gas grater than or equal to
+// CommitGasLimit.
+func (IBCMiddleware) processCallback(
+ ctx sdk.Context, callbackType types.CallbackType,
+ callbackData types.CallbackData, callbackExecutor func(sdk.Context) error,
+) (err error) {
+ cachedCtx, writeFn := ctx.CacheContext()
+ cachedCtx = cachedCtx.WithGasMeter(storetypes.NewGasMeter(callbackData.ExecutionGasLimit))
+
+ defer func() {
+ // consume the minimum of g.consumed and g.limit
+ ctx.GasMeter().ConsumeGas(cachedCtx.GasMeter().GasConsumedToLimit(), fmt.Sprintf("ibc %s callback", callbackType))
+
+ // recover from all panics except during SendPacket callbacks
+ if r := recover(); r != nil {
+ if callbackType == types.CallbackTypeSendPacket {
+ panic(r)
+ }
+ err = errorsmod.Wrapf(types.ErrCallbackPanic, "ibc %s callback panicked with: %v", callbackType, r)
+ }
+
+ // if the callback ran out of gas and the relayer has not reserved enough gas, then revert the state
+ if cachedCtx.GasMeter().IsPastLimit() {
+ if callbackData.AllowRetry() {
+ panic(storetypes.ErrorOutOfGas{Descriptor: fmt.Sprintf("ibc %s callback out of gas; commitGasLimit: %d", callbackType, callbackData.CommitGasLimit)})
+ }
+ err = errorsmod.Wrapf(types.ErrCallbackOutOfGas, "ibc %s callback out of gas", callbackType)
+ }
+
+ // allow the transaction to be committed, continuing the packet lifecycle
+ }()
+
+ err = callbackExecutor(cachedCtx)
+ if err == nil {
+ writeFn()
+ }
+
+ return err
+}
+```
+
+Chains are expected to specify a `maxCallbackGas` to ensure that callbacks do not consume an arbitrary amount of gas. Thus, it should always be possible for a relayer to complete the packet lifecycle even if the actor callbacks cannot run successfully.
+
+## Consequences
+
+### Positive
+
+- IBC Actors can now programmatically execute logic that involves sending a packet and then performing some additional logic once the packet lifecycle is complete
+- Middleware implementing ADR-8 can be generally used for any application
+- Leverages a similar callback architecture to the one used between core IBC and IBC applications
+
+### Negative
+
+- Callbacks may now have unbounded gas consumption since the actor may execute arbitrary logic. Chains implementing this feature should take care to place limitations on how much gas an actor callback can consume.
+- The relayer pays for the callback gas instead of the IBCActor
+
+### Neutral
+
+- Application packets that want to support ADR-8 must additionally have their packet data implement `PacketDataProvider` and `PacketData` interfaces.
+- Applications must implement `PacketDataUnmarshaler` interface
+- Callback receiving module must implement the `ContractKeeper` interface
+
+## References
+
+- [Original issue](https://github.com/cosmos/ibc-go/issues/1660)
+- [CallbackPacketData interface implementation](https://github.com/cosmos/ibc-go/pull/3287)
+- [ICS 20, ICS 27 implementations of the CallbackPacketData interface](https://github.com/cosmos/ibc-go/pull/3287)
diff --git a/ibc/next/architecture/adr-009-v6-ics27-msgserver.mdx b/ibc/next/architecture/adr-009-v6-ics27-msgserver.mdx
new file mode 100644
index 00000000..e9ac0430
--- /dev/null
+++ b/ibc/next/architecture/adr-009-v6-ics27-msgserver.mdx
@@ -0,0 +1,117 @@
+---
+title: "ADR 009: ICS27 Message Server Addition"
+---
+
+## Changelog
+
+- 07-09-2022: Initial draft
+
+## Status
+
+Accepted, implemented in v6 of ibc-go
+
+## Context
+
+ICS 27 (Interchain Accounts) brought a cross-chain account management protocol built upon IBC.
+It enabled chains to programmatically create accounts on behalf of counterparty chains which may enable a variety of authentication methods for this interchain account.
+The initial release of ICS 27 focused on enabling authentication schemes that may not require signing with a private key, such as via on-chain mechanisms like governance.
+
+Following the initial release of ICS 27 it became evident that:
+
+- a default authentication module would enable more usage of ICS 27
+- generic authentication modules should be capable of authenticating an interchain account registration
+- application logic which wraps ICS 27 packet sends does not need to be associated with the authentication logic
+
+## Decision
+
+The controller module should be simplified to remove the correlation between the authentication logic for an interchain account and the application logic for an interchain account.
+To minimize disruption to developers working on the original design of the ICS 27 controller module, all changes will be made in a backwards compatible fashion.
+
+### Msg server
+
+To achieve this, as stated by [@damiannolan](https://github.com/cosmos/ibc-go/issues/2026#issue-1341640594), it was proposed to:
+
+> Add a new `MsgServer` to `27-interchain-accounts` which exposes two distinct rpc endpoints:
+>
+> - `RegisterInterchainAccount`
+> - `SendTx`
+
+This will enable any SDK (authentication) module to register interchain accounts and send transactions on their behalf.
+Examples of existing SDK modules which would benefit from this change include:
+
+- x/auth
+- x/gov
+- x/group
+
+The existing go functions: `RegisterInterchainAccount()` and `SendTx()` will remain to operate as they did in previous release versions.
+
+This will be possible for SDK v0.46.x and above.
+
+### Allow `nil` underlying applications
+
+Authentication modules should interact with the controller module via the message server and should not be associated with application logic.
+For now, it will be allowed to set a `nil` underlying application.
+A future version may remove the underlying application entirely.
+
+See issue [#2040](https://github.com/cosmos/ibc-go/issues/2040)
+
+### Channel capability claiming
+
+The controller module will now claim the channel capability in `OnChanOpenInit`.
+Underlying applications will be passed a `nil` capability in `OnChanOpenInit`.
+
+Channel capability migrations will be added in two steps:
+
+- Upgrade handler migration which modifies the channel capability owner from the underlying app to the controller module
+- ICS 27 module automatic migration which asserts the upgrade handler channel capability migration has been performed successfully
+
+See issue [#2033](https://github.com/cosmos/ibc-go/issues/2033)
+
+### Middleware enabled channels
+
+In order to maintain backwards compatibility and avoid requiring underlying application developers to account for interchain accounts they did not register, a boolean mapping has been added to track the behaviour of how an account was created.
+
+If the account was created via the legacy API, then the underlying application callbacks will be executed.
+
+If the account was created with the new API (message server), then the underlying application callbacks will not be executed.
+
+See issue [#2145](https://github.com/cosmos/ibc-go/issues/2145)
+
+### Future considerations
+
+[ADR 008](https://github.com/cosmos/ibc-go/pull/1976) proposes the creation of a middleware which enables callers of an IBC packet send to perform application logic in conjunction with the IBC application.
+The underlying application can be removed at the availability of such a middleware as that will be the preferred method for executing application logic upon a ICS 27 packet send.
+
+### Miscellaneous
+
+In order to avoid import cycles, the genesis types have been moved to their own directory.
+A new protobuf package has been created for the genesis types.
+
+See PR [#2133](https://github.com/cosmos/ibc-go/pull/2133)
+
+An additional field has been added to the `ActiveChannel` type to store the `IsMiddlewareEnabled` field upon genesis import/export.
+
+See issue [#2165](https://github.com/cosmos/ibc-go/issues/2165)
+
+## Consequences
+
+### Positive
+
+- default authentication modules are provided (x/auth, x/group, x/gov)
+- any SDK authentication module may now be used with ICS 27
+- separation of authentication from application logic in relation to ICS 27
+- minimized disruption to existing development around ICS 27 controller module
+- underlying applications no longer have to handle capabilities
+- removal of the underlying application upon the creation of ADR 008 may be done in a minimally disruptive fashion
+- only underlying applications which registered the interchain account will perform application logic for that account (underlying applications do not need to be aware of accounts they did not register)
+
+### Negative
+
+- the security model has been reduced to that of the SDK. SDK modules may send packets for any interchain account.
+- additional maintenance of the messages added and the middleware enabled flag
+- underlying applications which will become ADR 008 modules are not required to be aware of accounts they did not register
+- calling legacy API vs the new API results in different behaviour for ICS 27 application stacks which have an underlying application
+
+### Neutral
+
+- A major release is required
diff --git a/ibc/next/architecture/adr-010-light-clients-as-sdk-modules.mdx b/ibc/next/architecture/adr-010-light-clients-as-sdk-modules.mdx
new file mode 100644
index 00000000..2bee6e88
--- /dev/null
+++ b/ibc/next/architecture/adr-010-light-clients-as-sdk-modules.mdx
@@ -0,0 +1,108 @@
+---
+title: "ADR 010: IBC Light Clients As SDK Modules"
+---
+
+## Changelog
+
+- 12-12-2022: initial draft
+
+## Status
+
+Proposed
+
+## Context
+
+ibc-go has 3 main consumers:
+
+- IBC light clients
+- IBC applications
+- relayers
+
+Relayers listen and respond to events emitted by ibc-go while IBC light clients and applications are invoked by core IBC.
+Currently there exists two different approaches to callbacks being invoked by core IBC.
+
+IBC light clients currently are invoked by a `ClientState` and `ConsensusState` interface as defined by [core IBC](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/core/exported/client.go#L36).
+The 02-client submodule will retrieve the `ClientState` or `ConsensusState` from the IBC store in order to perform callbacks to the light client.
+This design requires all required information for the light client to function to be stored in the `ClientState` or `ConsensusState` or potentially under metadata keys for a specific client instance.
+Additional information may be provided by core IBC via the defined interface arguments if that information is generic enough to be useful to all IBC light clients.
+This constraint has proved problematic as pass through clients (such as wasm) cannot maintain easy access to a VM instance.
+In addition, without increasing the size of the defined `ClientState` interface, light clients are unable to take advantage of basic built-in SDK functionality such as genesis import/export and migrations.
+
+The other approach used to perform callback logic is via registered SDK modules.
+This approach is used by core IBC to interact with IBC applications.
+IBC applications will register their callbacks on the IBC router at compile time.
+When a packet comes in, core IBC will use the IBC router to lookup the registered callback functions for the provided packet.
+The benefit of registered callbacks opposed to interface functions is that additional information may be accessed via external keepers.
+Because the IBC applications are also SDK modules, they additionally get access to a host of functionality provided by the SDK.
+This includes: genesis import/export, migrations, query/transaction CLI commands, type registration, gRPC query registration, and message server registration.
+
+As described in [ADR 006](./adr-006-02-client-refactor.md), generalizing light client behaviour is difficult.
+IBC light clients will obtain greater flexibility and control via the registered SDK module approach.
+
+## Decision
+
+Instead of using two different approaches to invoking callbacks, IBC light clients should be invoked as SDK modules.
+Over time and as necessary, core IBC should adjust its interactions with light clients such that they are SDK modules as opposed to interfaces.
+
+One immediate decision that has already been applied is to formalize light client type registration via the inclusion of an `AppModuleBasic` within the `ModuleManager` for a chain.
+The [tendermint](https://github.com/cosmos/ibc-go/pull/2825) and [solo machine](https://github.com/cosmos/ibc-go/pull/2826) clients were refactored to include this `AppModuleBasic` implementation and core IBC will no longer include either type as registered by default.
+
+Longer term solutions include using internal module communication as described in [ADR 033](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-033-protobuf-inter-module-comm.md) on the SDK.
+The following functions should become callbacks invoked via intermodule communication:
+
+- `Status`
+- `GetTimestampAtHeight`
+- `VerifyMembership`
+- `VerifyNonMembership`
+- `Initialize`
+- `VerifyClientMessage`
+- `CheckForMisbehaviour`
+- `UpdateStateOnMisbehaviour`
+- `UpdateState`
+- `CheckSubstituteAndUpdateState`
+- `VerifyUpgradeAndUpdateState`
+
+The ClientState interface should eventually be trimmed down to something along the lines of:
+
+```go
+type ClientState interface {
+ proto.Message
+
+ ClientType() string
+ GetLatestHeight() Height
+ Validate() error
+
+ ZeroCustomFields() ClientState
+
+ // ADDITION
+ Route() string // route used for intermodule communication
+}
+```
+
+For the most part, any functions which require access to the client store should likely not be an interface function of the `ClientState`.
+
+`ExportMetadata` should eventually be replaced by a light client's ability to import/export it's own genesis information.
+
+### Intermodule communication
+
+To keep the transition from interface callbacks to SDK module callbacks as simple as possible, intermodule communication (when available) should be used to route to light client modules.
+Without intermodule communication, a routing system would need to be developed/maintained to register callbacks.
+This functionality of routing to another SDK module should and will be provided by the SDK.
+Once it is possible to route to SDK modules, a `ClientState` type could expose the function `Route` which returns the callback route used to call the light client module.
+
+## Consequences
+
+### Positive
+
+- use a single approach for interacting with callbacks
+- greater flexibility and control for IBC light clients
+- does not require developing another routing system
+
+### Negative
+
+- requires breaking changes
+- requires waiting for intermodule communication
+
+### Neutral
+
+N/A
diff --git a/ibc/next/architecture/adr-011-transfer-total-escrow-state-entry.mdx b/ibc/next/architecture/adr-011-transfer-total-escrow-state-entry.mdx
new file mode 100644
index 00000000..a23ae88f
--- /dev/null
+++ b/ibc/next/architecture/adr-011-transfer-total-escrow-state-entry.mdx
@@ -0,0 +1,147 @@
+---
+title: "ADR 011: ICS-20 Transfer State Entry For Total Amount Of Tokens In Escrow"
+---
+
+## Changelog
+
+- 24-05-2023: Initial draft
+
+## Status
+
+Accepted and applied in v7.1 of ibc-go
+
+## Context
+
+Every ICS-20 transfer channel has its own escrow bank account. This account is used to lock tokens that are transferred out of a chain that acts as the source of the tokens (i.e. when the tokens being transferred have not returned to the originating chain). This design makes it easy to query the balance of the escrow accounts and find out the total amount of tokens in escrow in a particular channel. However, there are use cases where it would be useful to determine the total escrowed amount of a given denomination across all channels where those tokens have been transferred out.
+
+For example: assuming that there are three channels between Cosmos Hub to Osmosis and 10 ATOM have been transferred from the Cosmos Hub to Osmosis on each of those channels, then we would like to know that 30 ATOM have been transferred (i.e. are locked in the escrow accounts of each channel) without needing to iterate over each escrow account to add up the balances of each.
+
+For a sample use case where this feature would be useful, please refer to Osmosis' rate limiting use case described in [#2664](https://github.com/cosmos/ibc-go/issues/2664).
+
+## Decision
+
+### State entry denom -> amount
+
+The total amount of tokens in escrow (across all transfer channels) for a given denomination is stored in state in an entry keyed by the denomination: `totalEscrowForDenom/{denom}`.
+
+### Panic if amount is negative
+
+If a negative amount is ever attempted to be stored, then the keeper function will panic:
+
+```go
+if coin.Amount.IsNegative() {
+ panic(fmt.Sprintf("amount cannot be negative: %s", coin.Amount))
+}
+```
+
+### Delete state entry if amount is zero
+
+When setting the amount for a particular denomination, the value might be zero if all tokens that were transferred out of the chain have been transferred back. If this happens, then the state entry for this particular denomination will be deleted, since Cosmos SDK's `x/bank` module prunes any non-zero balances:
+
+```go
+if coin.Amount.IsZero() {
+ store.Delete(key) // delete the key since Cosmos SDK x/bank module will prune any non-zero balances
+ return
+}
+```
+
+### Bundle escrow/unescrow with setting state entry
+
+Two new functions are implemented that bundle together the operations of escrowing/unescrowing and setting the total escrow amount in state, since these operations need to be executed together.
+
+For escrowing tokens:
+
+```go
+// escrowToken will send the given token from the provided sender to the escrow address. It will also
+// update the total escrowed amount by adding the escrowed token to the current total escrow.
+func (k Keeper) escrowToken(ctx sdk.Context, sender, escrowAddress sdk.AccAddress, token sdk.Coin) error {
+ if err := k.bankKeeper.SendCoins(ctx, sender, escrowAddress, sdk.NewCoins(token)); err != nil {
+ // failure is expected for insufficient balances
+ return err
+ }
+
+ // track the total amount in escrow keyed by denomination to allow for efficient iteration
+ currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom())
+ newTotalEscrow := currentTotalEscrow.Add(token)
+ k.SetTotalEscrowForDenom(ctx, newTotalEscrow)
+
+ return nil
+}
+```
+
+For unescrowing tokens:
+
+```go
+// unescrowToken will send the given token from the escrow address to the provided receiver. It will also
+// update the total escrow by deducting the unescrowed token from the current total escrow.
+func (k Keeper) unescrowToken(ctx sdk.Context, escrowAddress, receiver sdk.AccAddress, token sdk.Coin) error {
+ if err := k.bankKeeper.SendCoins(ctx, escrowAddress, receiver, sdk.NewCoins(token)); err != nil {
+ // NOTE: this error is only expected to occur given an unexpected bug or a malicious
+ // counterparty module. The bug may occur in bank or any part of the code that allows
+ // the escrow address to be drained. A malicious counterparty module could drain the
+ // escrow address by allowing more tokens to be sent back then were escrowed.
+ return errorsmod.Wrap(err, "unable to unescrow tokens, this may be caused by a malicious counterparty module or a bug: please open an issue on counterparty module")
+ }
+
+ // track the total amount in escrow keyed by denomination to allow for efficient iteration
+ currentTotalEscrow := k.GetTotalEscrowForDenom(ctx, token.GetDenom())
+ newTotalEscrow := currentTotalEscrow.Sub(token)
+ k.SetTotalEscrowForDenom(ctx, newTotalEscrow)
+
+ return nil
+}
+```
+
+When tokens need to be escrowed in `sendTransfer`, then `escrowToken` is called; when tokens need to be unescrowed on execution of the `OnRecvPacket`, `OnAcknowledgementPacket` or `OnTimeoutPacket` callbacks, then `unescrowToken` is called.
+
+### gRPC query endpoint and CLI to retrieve amount
+
+A gRPC query endpoint is added so that it is possible to retrieve the total amount for a given denomination:
+
+```proto
+// TotalEscrowForDenom returns the total amount of tokens in escrow based on the denom.
+rpc TotalEscrowForDenom(QueryTotalEscrowForDenomRequest) returns (QueryTotalEscrowForDenomResponse) {
+ option (google.api.http).get = "/ibc/apps/transfer/v1/denoms/{denom=**}/total_escrow";
+}
+
+// QueryTotalEscrowForDenomRequest is the request type for TotalEscrowForDenom RPC method.
+message QueryTotalEscrowForDenomRequest {
+ string denom = 1;
+}
+
+// QueryTotalEscrowForDenomResponse is the response type for TotalEscrowForDenom RPC method.
+message QueryTotalEscrowForDenomResponse {
+ cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false];
+}
+```
+
+And a CLI query is also available to retrieve the total amount via the command line:
+
+```shell
+query ibc-transfer total-escrow [denom]
+```
+
+## Consequences
+
+### Positive
+
+- Possibility to retrieve the total amount of a particular denomination in escrow across all transfer channels without iteration.
+
+### Negative
+
+No notable consequences
+
+### Neutral
+
+- A new entry is added to state for every denomination that is transferred out of the chain.
+
+## References
+
+Issues:
+
+- [#2664](https://github.com/cosmos/ibc-go/issues/2664)
+
+PRs:
+
+- [#3019](https://github.com/cosmos/ibc-go/pull/3019)
+- [#3558](https://github.com/cosmos/ibc-go/pull/3558)
diff --git a/ibc/next/architecture/adr-015-ibc-packet-receiver.mdx b/ibc/next/architecture/adr-015-ibc-packet-receiver.mdx
new file mode 100644
index 00000000..d85ba9b0
--- /dev/null
+++ b/ibc/next/architecture/adr-015-ibc-packet-receiver.mdx
@@ -0,0 +1,301 @@
+---
+title: "ADR 015: IBC Packet Receiver"
+---
+
+## Changelog
+
+- 22-10-2019: Initial Draft
+
+## Context
+
+[ICS 26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module) defines a function [`handlePacketRecv`](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module#packet-relay).
+
+In ICS 26, the routing module is defined as a layer above each application module
+which verifies and routes messages to the destination modules. It is possible to
+implement it as a separate module, however, we already have the functionality to route
+messages upon the destination identifiers in the baseapp. This ADR suggests
+to utilize existing `baseapp.router` to route packets to application modules.
+
+Generally, routing module callbacks have two separate steps in them,
+verification and execution. This corresponds to the `AnteHandler`-`Handler`
+model inside the SDK. We can do the verification inside the `AnteHandler`
+in order to increase developer ergonomics by reducing boilerplate
+verification code.
+
+For atomic multi-message transaction, we want to keep the IBC related
+state modification to be preserved even the application side state change
+reverts. One of the example might be IBC token sending message following with
+stake delegation which uses the tokens received by the previous packet message.
+If the token receiving fails for any reason, we might not want to keep
+executing the transaction, but we also don't want to abort the transaction
+or the sequence and commitment will be reverted and the channel will be stuck.
+This ADR suggests new `CodeType`, `CodeTxBreak`, to fix this problem.
+
+## Decision
+
+`PortKeeper` will have the capability key that is able to access only the
+channels bound to the port. Entities that hold a `PortKeeper` will be
+able to call the methods on it which are corresponding with the methods with
+the same names on the `ChannelKeeper`, but only with the
+allowed port. `ChannelKeeper.Port(string, ChannelChecker)` will be defined to
+easily construct a capability-safe `PortKeeper`. This will be addressed in
+another ADR and we will use insecure `ChannelKeeper` for now.
+
+`baseapp.runMsgs` will break the loop over the messages if one of the handlers
+returns `!Result.IsOK()`. However, the outer logic will write the cached
+store if `Result.IsOK() || Result.Code.IsBreak()`. `Result.Code.IsBreak()` if
+`Result.Code == CodeTxBreak`.
+
+```go
+func (app *BaseApp) runTx(tx Tx) (result Result) {
+ msgs := tx.GetMsgs()
+
+ // AnteHandler
+ if app.anteHandler != nil {
+ anteCtx, msCache := app.cacheTxContext(ctx)
+ newCtx, err := app.anteHandler(anteCtx, tx)
+ if !newCtx.IsZero() {
+ ctx = newCtx.WithMultiStore(ms)
+ }
+
+ if err != nil {
+ // error handling logic
+ return res
+ }
+
+ msCache.Write()
+ }
+
+ // Main Handler
+ runMsgCtx, msCache := app.cacheTxContext(ctx)
+ result = app.runMsgs(runMsgCtx, msgs)
+ // BEGIN modification made in this ADR
+ if result.IsOK() || result.IsBreak() {
+ // END
+ msCache.Write()
+ }
+
+ return result
+}
+```
+
+The Cosmos SDK will define an `AnteDecorator` for IBC packet receiving. The
+`AnteDecorator` will iterate over the messages included in the transaction, type
+`switch` to check whether the message contains an incoming IBC packet, and if so
+verify the Merkle proof.
+
+```go
+type ProofVerificationDecorator struct {
+ clientKeeper ClientKeeper
+ channelKeeper ChannelKeeper
+}
+
+func (pvr ProofVerificationDecorator) AnteHandle(ctx Context, tx Tx, simulate bool, next AnteHandler) (Context, error) {
+ for _, msg := range tx.GetMsgs() {
+ var err error
+ switch msg := msg.(type) {
+ case client.MsgUpdateClient:
+ err = pvr.clientKeeper.UpdateClient(msg.ClientID, msg.Header)
+ case channel.MsgPacket:
+ err = pvr.channelKeeper.RecvPacket(msg.Packet, msg.Proofs, msg.ProofHeight)
+ case channel.MsgAcknowledgement:
+ err = pvr.channelKeeper.AcknowledgementPacket(msg.Acknowledgement, msg.Proof, msg.ProofHeight)
+ case channel.MsgTimeoutPacket:
+ err = pvr.channelKeeper.TimeoutPacket(msg.Packet, msg.Proof, msg.ProofHeight, msg.NextSequenceRecv)
+ case channel.MsgChannelOpenInit;
+ err = pvr.channelKeeper.CheckOpen(msg.PortID, msg.ChannelID, msg.Channel)
+ default:
+ continue
+ }
+
+ if err != nil {
+ return ctx, err
+ }
+ }
+
+ return next(ctx, tx, simulate)
+}
+```
+
+Where `MsgUpdateClient`, `MsgPacket`, `MsgAcknowledgement`, `MsgTimeoutPacket`
+are `sdk.Msg` types correspond to `handleUpdateClient`, `handleRecvPacket`,
+`handleAcknowledgementPacket`, `handleTimeoutPacket` of the routing module,
+respectively.
+
+The side effects of `RecvPacket`, `VerifyAcknowledgement`,
+`VerifyTimeout` will be extracted out into separated functions,
+`WriteAcknowledgement`, `DeleteCommitment`, `DeleteCommitmentTimeout`, respectively,
+which will be called by the application handlers after the execution.
+
+`WriteAcknowledgement` writes the acknowledgement to the state that can be
+verified by the counter-party chain and increments the sequence to prevent
+double execution. `DeleteCommitment` will delete the commitment stored,
+`DeleteCommitmentTimeout` will delete the commitment and close channel in case
+of ordered channel.
+
+```go
+func (keeper ChannelKeeper) WriteAcknowledgement(ctx Context, packet Packet, ack []byte) {
+ keeper.SetPacketAcknowledgement(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence(), ack)
+ keeper.SetNextSequenceRecv(ctx, packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
+}
+
+func (keeper ChannelKeeper) DeleteCommitment(ctx Context, packet Packet) {
+ keeper.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
+}
+
+func (keeper ChannelKeeper) DeleteCommitmentTimeout(ctx Context, packet Packet) {
+ k.deletePacketCommitment(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
+
+ if channel.Ordering == types.ORDERED [
+ channel.State = types.CLOSED
+ k.SetChannel(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), channel)
+ }
+}
+```
+
+Each application handler should call respective finalization methods on the `PortKeeper`
+in order to increase sequence (in case of packet) or remove the commitment
+(in case of acknowledgement and timeout).
+Calling those functions implies that the application logic has successfully executed.
+However, the handlers can return `Result` with `CodeTxBreak` after calling those methods
+which will persist the state changes that has been already done but prevent any further
+messages to be executed in case of semantically invalid packet. This will keep the sequence
+increased in the previous IBC packets(thus preventing double execution) without
+proceeding to the following messages.
+In any case the application modules should never return state reverting result,
+which will make the channel unable to proceed.
+
+`ChannelKeeper.CheckOpen` method will be introduced. This will replace `onChanOpen*` defined
+under the routing module specification. Instead of define each channel handshake callback
+functions, application modules can provide `ChannelChecker` function with the `AppModule`
+which will be injected to `ChannelKeeper.Port()` at the top level application.
+`CheckOpen` will find the correct `ChannelChecker` using the
+`PortID` and call it, which will return an error if it is unacceptable by the application.
+
+The `ProofVerificationDecorator` will be inserted to the top level application.
+It is not safe to make each module responsible to call proof verification
+logic, whereas application can misbehave(in terms of IBC protocol) by
+mistake.
+
+The `ProofVerificationDecorator` should come right after the default sybil attack
+resistant layer from the current `auth.NewAnteHandler`:
+
+```go
+// add IBC ProofVerificationDecorator to the Chain of
+func NewAnteHandler(
+ ak keeper.AccountKeeper, supplyKeeper types.SupplyKeeper, ibcKeeper ibc.Keeper,
+ sigGasConsumer SignatureVerificationGasConsumer) sdk.AnteHandler {
+ return sdk.ChainAnteDecorators(
+ NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
+ ...
+ NewIncrementSequenceDecorator(ak),
+ ibcante.ProofVerificationDecorator(ibcKeeper.ClientKeeper, ibcKeeper.ChannelKeeper), // innermost AnteDecorator
+ )
+}
+```
+
+The implementation of this ADR will also create a `Data` field of the `Packet` of type `[]byte`, which can be deserialised by the receiving module into its own private type. It is up to the application modules to do this according to their own interpretation, not by the IBC keeper. This is crucial for dynamic IBC.
+
+Example application-side usage:
+
+```go
+type AppModule struct {}
+
+// CheckChannel will be provided to the ChannelKeeper as ChannelKeeper.Port(module.CheckChannel)
+func (module AppModule) CheckChannel(portID, channelID string, channel Channel) error {
+ if channel.Ordering != UNORDERED {
+ return ErrUncompatibleOrdering()
+ }
+ if channel.CounterpartyPort != "bank" {
+ return ErrUncompatiblePort()
+ }
+ if channel.Version != "" {
+ return ErrUncompatibleVersion()
+ }
+ return nil
+}
+
+func NewHandler(k Keeper) Handler {
+ return func(ctx Context, msg Msg) Result {
+ switch msg := msg.(type) {
+ case MsgTransfer:
+ return handleMsgTransfer(ctx, k, msg)
+ case ibc.MsgPacket:
+ var data PacketDataTransfer
+ if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil {
+ return err
+ }
+ return handlePacketDataTransfer(ctx, k, msg, data)
+ case ibc.MsgTimeoutPacket:
+ var data PacketDataTransfer
+ if err := types.ModuleCodec.UnmarshalBinaryBare(msg.GetData(), &data); err != nil {
+ return err
+ }
+ return handleTimeoutPacketDataTransfer(ctx, k, packet)
+ // interface { PortID() string; ChannelID() string; Channel() ibc.Channel }
+ // MsgChanInit, MsgChanTry implements ibc.MsgChannelOpen
+ case ibc.MsgChannelOpen:
+ return handleMsgChannelOpen(ctx, k, msg)
+ }
+ }
+}
+
+func handleMsgTransfer(ctx Context, k Keeper, msg MsgTransfer) Result {
+ err := k.SendTransfer(ctx,msg.PortID, msg.ChannelID, msg.Amount, msg.Sender, msg.Receiver)
+ if err != nil {
+ return sdk.ResultFromError(err)
+ }
+
+ return sdk.Result{}
+}
+
+func handlePacketDataTransfer(ctx Context, k Keeper, packet Packet, data PacketDataTransfer) Result {
+ err := k.ReceiveTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data)
+ if err != nil {
+ // TODO: Source chain sent invalid packet, shutdown channel
+ }
+ k.ChannelKeeper.WriteAcknowledgement([]byte{0x00}) // WriteAcknowledgement increases the sequence, preventing double spending
+ return sdk.Result{}
+}
+
+func handleCustomTimeoutPacket(ctx Context, k Keeper, packet CustomPacket) Result {
+ err := k.RecoverTransfer(ctx, packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetDestinationPort(), packet.GetDestinationChannel(), data)
+ if err != nil {
+ // This chain sent invalid packet or cannot recover the funds
+ panic(err)
+ }
+ k.ChannelKeeper.DeleteCommitmentTimeout(ctx, packet)
+ // packet timeout should not fail
+ return sdk.Result{}
+}
+
+func handleMsgChannelOpen(sdk.Context, k Keeper, msg MsgOpenChannel) Result {
+ k.AllocateEscrowAddress(ctx, msg.ChannelID())
+ return sdk.Result{}
+}
+```
+
+## Status
+
+Proposed
+
+## Consequences
+
+### Positive
+
+- Intuitive interface for developers - IBC handlers do not need to care about IBC authentication
+- State change commitment logic is embedded into `baseapp.runTx` logic
+
+### Negative
+
+- Cannot support dynamic ports, routing is tied to the baseapp router
+
+### Neutral
+
+- Introduces new `AnteHandler` decorator.
+- Dynamic ports can be supported using hierarchical port identifier, see #5290 for detail
+
+## References
+
+- Relevant comment: [cosmos/ics#289](https://github.com/cosmos/ibc/issues/289#issuecomment-544533583)
+- [ICS26 - Routing Module](https://github.com/cosmos/ibc/tree/master/spec/core/ics-026-routing-module)
diff --git a/ibc/next/architecture/adr-025-ibc-passive-channels.mdx b/ibc/next/architecture/adr-025-ibc-passive-channels.mdx
new file mode 100644
index 00000000..84e4710b
--- /dev/null
+++ b/ibc/next/architecture/adr-025-ibc-passive-channels.mdx
@@ -0,0 +1,143 @@
+---
+title: "ADR 025: IBC Passive Channels"
+---
+
+## Changelog
+
+- 23-04-2021: Change status to "deprecated"
+- 23-05-2020: Provide sample Go code and more details
+- 18-05-2020: Initial Draft
+
+## Status
+
+*deprecated*
+
+## Context
+
+The current "naive" IBC Relayer strategy currently establishes a single predetermined IBC channel atop a single connection between two clients (each potentially of a different chain). This strategy then detects packets to be relayed by watching for `send_packet` and `recv_packet` events matching that channel, and sends the necessary transactions to relay those packets.
+
+We wish to expand this "naive" strategy to a "passive" one which detects and relays both channel handshake messages and packets on a given connection, without the need to know each channel in advance of relaying it.
+
+In order to accomplish this, we propose adding more comprehensive events to expose channel metadata for each transaction sent from the `x/ibc/core/04-channel/keeper/handshake.go` and `x/ibc/core/04-channel/keeper/packet.go` modules.
+
+Here is an example of what would be in `ChanOpenInit`:
+
+```go
+const (
+ EventTypeChannelMeta = "channel_meta"
+ AttributeKeyAction = "action"
+ AttributeKeyHops = "hops"
+ AttributeKeyOrder = "order"
+ AttributeKeySrcPort = "src_port"
+ AttributeKeySrcChannel = "src_channel"
+ AttributeKeySrcVersion = "src_version"
+ AttributeKeyDstPort = "dst_port"
+ AttributeKeyDstChannel = "dst_channel"
+ AttributeKeyDstVersion = "dst_version"
+)
+// ...
+// Emit Event with Channel metadata for the relayer to pick up and
+// relay to the other chain
+// This appears immediately before the successful return statement.
+ctx.EventManager().EmitEvents(sdk.Events{
+ sdk.NewEvent(
+ types.EventTypeChannelMeta,
+ sdk.NewAttribute(types.AttributeKeyAction, "open_init"),
+ sdk.NewAttribute(types.AttributeKeySrcConnection, connectionHops[0]),
+ sdk.NewAttribute(types.AttributeKeyHops, strings.Join(connectionHops, ",")),
+ sdk.NewAttribute(types.AttributeKeyOrder, order.String()),
+ sdk.NewAttribute(types.AttributeKeySrcPort, portID),
+ sdk.NewAttribute(types.AttributeKeySrcChannel, channelID),
+ sdk.NewAttribute(types.AttributeKeySrcVersion, version),
+ sdk.NewAttribute(types.AttributeKeyDstPort, counterparty.GetPortID()),
+ sdk.NewAttribute(types.AttributeKeyDstChannel, counterparty.GetChannelID()),
+ // The destination version is not yet known, but a value is necessary to pad
+ // the event attribute offsets
+ sdk.NewAttribute(types.AttributeKeyDstVersion, ""),
+ ),
+})
+```
+
+These metadata events capture all the "header" information needed to route IBC channel handshake transactions without requiring the client to query any data except that of the connection ID that it is willing to relay. It is intended that `channel_meta.src_connection` is the only event key that needs to be indexed for a passive relayer to function.
+
+### Handling Channel Open Attempts
+
+In the case of the passive relayer, when one chain sends a `ChanOpenInit`, the relayer should inform the other chain of this open attempt and allow that chain to decide how (and if) it continues the handshake. Once both chains have actively approved the channel opening, then the rest of the handshake can happen as it does with the current "naive" relayer.
+
+To implement this behavior, we propose replacing the `cbs.OnChanOpenTry` callback with a new `cbs.OnAttemptChanOpenTry` callback which explicitly handles the `MsgChannelOpenTry`, usually by resulting in a call to `keeper.ChanOpenTry`. The typical implementation, in `x/ibc-transfer/module.go` would be compatible with the current "naive" relayer, as follows:
+
+```go
+func (am AppModule) OnAttemptChanOpenTry(
+ ctx sdk.Context,
+ chanKeeper channel.Keeper,
+ portCap *capability.Capability,
+ msg channel.MsgChannelOpenTry,
+) (*sdk.Result, error) {
+ // Require portID is the portID transfer module is bound to
+ boundPort := am.keeper.GetPort(ctx)
+ if boundPort != msg.PortID {
+ return nil, sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", msg.PortID, boundPort)
+ }
+
+ // BEGIN NEW CODE
+ // Assert our protocol version, overriding the relayer's suggestion.
+ msg.Version = types.Version
+ // Continue the ChanOpenTry.
+ res, chanCap, err := channel.HandleMsgChannelOpenTry(ctx, chanKeeper, portCap, msg)
+ if err != nil {
+ return nil, err
+ }
+ // END OF NEW CODE
+
+ // ... the rest of the callback is similar to the existing OnChanOpenTry
+ // but uses msg.* directly.
+```
+
+Here is how this callback would be used, in the implementation of `x/ibc/handler.go`:
+
+```go
+// ...
+case channel.MsgChannelOpenTry:
+ // Lookup module by port capability
+ module, portCap, err := k.PortKeeper.LookupModuleByPort(ctx, msg.PortID)
+ if err != nil {
+ return nil, sdkerrors.Wrap(err, "could not retrieve module from port-id")
+ }
+ // Retrieve callbacks from router
+ cbs, ok := k.Router.GetRoute(module)
+ if !ok {
+ return nil, sdkerrors.Wrapf(port.ErrInvalidRoute, "route not found to module: %s", module)
+ }
+ // Delegate to the module's OnAttemptChanOpenTry.
+ return cbs.OnAttemptChanOpenTry(ctx, k.ChannelKeeper, portCap, msg)
+```
+
+The reason we do not have a more structured interaction between `x/ibc/handler.go` and the port's module (to explicitly negotiate versions, etc) is that we do not wish to constrain the app module to have to finish handling the `MsgChannelOpenTry` during this transaction or even this block.
+
+## Decision
+
+- Expose events to allow "passive" connection relayers.
+- Enable application-initiated channels via such passive relayers.
+- Allow port modules to control how to handle open-try messages.
+
+## Consequences
+
+### Positive
+
+Makes channels into a complete application-level abstraction.
+
+Applications have full control over initiating and accepting channels, rather than expecting a relayer to tell them when to do so.
+
+A passive relayer does not have to know what kind of channel (version string, ordering constraints, firewalling logic) the application supports. These are negotiated directly between applications.
+
+### Negative
+
+Increased event size for IBC messages.
+
+### Neutral
+
+More IBC events are exposed.
+
+## References
+
+- The Agoric VM's IBC handler currently [accommodates `attemptChanOpenTry`](https://github.com/Agoric/agoric-sdk/blob/904b3a0423222a1b32893453e44bbde598473960/packages/cosmic-swingset/lib/ag-solo/vats/ibc.js#L546)
diff --git a/ibc/next/architecture/adr-026-ibc-client-recovery-mechanisms.mdx b/ibc/next/architecture/adr-026-ibc-client-recovery-mechanisms.mdx
new file mode 100644
index 00000000..6a22b53a
--- /dev/null
+++ b/ibc/next/architecture/adr-026-ibc-client-recovery-mechanisms.mdx
@@ -0,0 +1,92 @@
+---
+title: "ADR 026: IBC Client Recovery Mechanisms"
+---
+
+## Changelog
+
+- 23-06-2020: Initial version
+- 06-08-2020: Revisions per review & to reference version
+- 15-01-2021: Revision to support substitute clients for unfreezing
+- 20-05-2021: Revision to simplify consensus state copying, remove initial height
+- 08-04-2022: Revision to deprecate AllowUpdateAfterExpiry and AllowUpdateAfterMisbehaviour
+- 15-07-2022: Revision to allow updating of TrustingPeriod
+- 05-09-2023: Revision to migrate from gov v1beta1 to gov v1
+
+## Status
+
+*Accepted*
+
+## Context
+
+### Summary
+
+At launch, IBC will be a novel protocol, without an experienced user-base. At the protocol layer, it is not possible to distinguish between client expiry or misbehaviour due to genuine faults (Byzantine behaviour) and client expiry or misbehaviour due to user mistakes (failing to update a client, or accidentally double-signing). In the base IBC protocol and ICS 20 fungible token transfer implementation, if a client can no longer be updated, funds in that channel will be permanently locked and can no longer be transferred. To the degree that it is safe to do so, it would be preferable to provide users with a recovery mechanism which can be utilised in these exceptional cases.
+
+### Exceptional cases
+
+The state of concern is where a client associated with connection(s) and channel(s) can no longer be updated. This can happen for several reasons:
+
+1. The chain which the client is following has halted and is no longer producing blocks/headers, so no updates can be made to the client
+1. The chain which the client is following has continued to operate, but no relayer has submitted a new header within the unbonding period, and the client has expired
+ 1. This could be due to real misbehaviour (intentional Byzantine behaviour) or merely a mistake by validators, but the client cannot distinguish these two cases
+1. The chain which the client is following has experienced a misbehaviour event, and the client has been frozen & thus can no longer be updated
+
+### Security model
+
+Two-thirds of the validator set (the quorum for governance, module participation) can already sign arbitrary data, so allowing governance to manually force-update a client with a new header after a delay period does not substantially alter the security model.
+
+## Decision
+
+We elect not to deal with chains which have actually halted, which is necessarily Byzantine behaviour and in which case token recovery is not likely possible anyways (in-flight packets cannot be timed-out, but the relative impact of that is minor).
+
+1. Require Tendermint light clients (ICS 07) to be created with the following additional flags
+ 1. `allow_update_after_expiry` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced.
+1. Require Tendermint light clients (ICS 07) to expose the following additional internal query functions
+ 1. `Expired() boolean`, which returns whether or not the client has passed the trusting period since the last update (in which case no headers can be validated)
+1. Require Tendermint light clients (ICS 07) & solo machine clients (ICS 06) to be created with the following additional flags
+ 1. `allow_update_after_misbehaviour` (boolean, default true). Note that this flag has been deprecated, it remains to signal intent but checks against this value will not be enforced.
+1. Require Tendermint light clients (ICS 07) to expose the following additional state mutation functions
+ 1. `Unfreeze()`, which unfreezes a light client after misbehaviour and clears any frozen height previously set
+1. Add a new governance proposal with `MsgRecoverClient`.
+ 1. Create a new Msg with two client identifiers (`string`) and a signer.
+ 1. The first client identifier is the proposed client to be updated. This client must be either frozen or expired.
+ 1. The second client is a substitute client. It carries all the state for the client which may be updated. It must have identical client and chain parameters to the client which may be updated (except for latest height, frozen height, and chain-id). It should be continually updated during the voting period.
+ 1. If this governance proposal passes, the client on trial will be updated to the latest state of the substitute.
+ 1. The signer must be the authority set for the ibc module.
+
+ Previously, `AllowUpdateAfterExpiry` and `AllowUpdateAfterMisbehaviour` were used to signal the recovery options for an expired or frozen client, and governance proposals were not allowed to overwrite the client if these parameters were set to false. However, this has now been deprecated because a code migration can overwrite the client and consensus states regardless of the value of these parameters. If governance would vote to overwrite a client or consensus state, it is likely that governance would also be willing to perform a code migration to do the same.
+
+ In addition, `TrustingPeriod` was initially not allowed to be updated by a client upgrade proposal. However, due to the number of situations experienced in production where the `TrustingPeriod` of a client should be allowed to be updated because of ie: initial misconfiguration for a canonical channel, governance should be allowed to update this client parameter.
+
+ In versions older than ibc-go v8, `MsgRecoverClient` was a governance proposal type `ClientUpdateProposal`. It has been removed and replaced by `MsgRecoverClient` in the migration from governance v1beta1 to governance v1.
+
+ Note that this should NOT be lightly updated, as there may be a gap in time between when misbehaviour has occurred and when the evidence of misbehaviour is submitted. For example, if the `UnbondingPeriod` is 2 weeks and the `TrustingPeriod` has also been set to two weeks, a validator could wait until right before `UnbondingPeriod` finishes, submit false information, then unbond and exit without being slashed for misbehaviour. Therefore, we recommend that the trusting period for the 07-tendermint client be set to 2/3 of the `UnbondingPeriod`.
+
+Note that clients frozen due to misbehaviour must wait for the evidence to expire to avoid becoming refrozen.
+
+This ADR does not address planned upgrades, which are handled separately as per the [specification](https://github.com/cosmos/ibc/tree/master/spec/client/ics-007-tendermint-client#upgrades).
+
+## Consequences
+
+### Positive
+
+- Establishes a mechanism for client recovery in the case of expiry
+- Establishes a mechanism for client recovery in the case of misbehaviour
+- Constructing an ClientUpdate Proposal is as difficult as creating a new client
+
+### Negative
+
+- Additional complexity in client creation which must be understood by the user
+- Coping state of the substitute adds complexity
+- Governance participants must vote on a substitute client
+
+### Neutral
+
+No neutral consequences.
+
+## References
+
+- [Prior discussion](https://github.com/cosmos/ibc/issues/421)
+- [Epoch number discussion](https://github.com/cosmos/ibc/issues/439)
+- [Upgrade plan discussion](https://github.com/cosmos/ibc/issues/445)
+- [Migration from gov v1beta1 to gov v1](https://github.com/cosmos/ibc-go/issues/3672)
diff --git a/ibc/next/architecture/adr-027-ibc-wasm.mdx b/ibc/next/architecture/adr-027-ibc-wasm.mdx
new file mode 100644
index 00000000..20d3421c
--- /dev/null
+++ b/ibc/next/architecture/adr-027-ibc-wasm.mdx
@@ -0,0 +1,169 @@
+---
+title: "ADR 27: Add Support For Wasm Based Light Client"
+---
+
+## Changelog
+
+- 26-11-2020: Initial Draft
+- 26-05-2023: Update after 02-client refactor and re-implementation by Strangelove
+- 13-12-2023: Update after upstreaming of module to ibc-go
+
+## Status
+
+*Accepted and applied in v0.1.0 of 08-wasm*
+
+## Abstract
+
+In the Cosmos SDK light clients are currently hardcoded in Go. This makes upgrading existing IBC light clients or
+adding support for new light client a multi step process involving on-chain governance which is time-consuming.
+
+To remedy this, we are proposing a Wasm VM to host light client bytecode, which allows easier upgrading of
+existing IBC light clients as well as adding support for new IBC light clients without requiring a code release and
+corresponding hard-fork event.
+
+## Context
+
+Currently in ibc-go light clients are defined as part of the codebase and are implemented as modules under
+`modules/light-clients`. Adding support for new light clients or updating an existing light client in the event
+of a security issue or consensus update is a multi-step process which is both time-consuming and error-prone.
+In order to enable new IBC light client implementations it is necessary to modify the codebase of ibc-go (if the light
+client is part of its codebase), re-build chains' binaries, pass a governance proposal and validators upgrade their nodes.
+
+Another problem stemming from the above process is that if a chain wants to upgrade its own consensus, it will
+need to convince every chain or hub connected to it to upgrade its light client in order to stay connected. Due
+to the time-consuming process required to upgrade a light client, a chain with lots of connections needs to be
+disconnected for quite some time after upgrading its consensus, which can be very expensive in terms of time and effort.
+
+We are proposing simplifying this workflow by integrating a Wasm light client module that makes adding support for
+new light clients a simple governance-gated transaction. The light client bytecode, written in Wasm-compilable Rust,
+runs inside a Wasm VM. The Wasm light client submodule exposes a proxy light client interface that routes incoming
+messages to the appropriate handler function, inside the Wasm VM for execution.
+
+With the Wasm light client module, anybody can add new IBC light client in the form of Wasm bytecode (provided they are
+able to submit the governance proposal transaction and that it passes) as well as instantiate clients using any created
+client type. This allows any chain to update its own light client in other chains without going through the steps outlined above.
+
+## Decision
+
+We decided to implement the Wasm light client module as a light client proxy that will interface with the actual light client
+uploaded as Wasm bytecode. To enable usage of the Wasm light client module, users need to add it to the list of allowed clients
+by updating the `AllowedClients` parameter in the 02-client submodule of core IBC.
+
+```go
+params := clientKeeper.GetParams(ctx)
+params.AllowedClients = append(params.AllowedClients, exported.Wasm)
+clientKeeper.SetParams(ctx, params)
+```
+
+Adding a new light client contract is governance-gated. To upload a new light client users need to submit
+a [governance v1 proposal](/sdk/v0.53/build/modules/gov#proposals) that contains the `sdk.Msg` for storing
+the Wasm contract's bytecode. The required message is `MsgStoreCode` and the bytecode is provided in the field `wasm_byte_code`:
+
+```proto
+// MsgStoreCode defines the request type for the StoreCode rpc.
+message MsgStoreCode {
+ // signer address
+ string signer = 1;
+ // wasm byte code of light client contract. It can be raw or gzip compressed
+ bytes wasm_byte_code = 2;
+}
+```
+
+The RPC handler processing `MsgStoreCode` will make sure that the signer of the message matches the address of authority allowed to
+submit this message (which is normally the address of the governance module).
+
+```go
+// StoreCode defines a rpc handler method for MsgStoreCode
+func (k Keeper) StoreCode(goCtx context.Context, msg *types.MsgStoreCode) (*types.MsgStoreCodeResponse, error) {
+ if k.GetAuthority() != msg.Signer {
+ return nil, errorsmod.Wrapf(ibcerrors.ErrUnauthorized, "expected %s, got %s", k.GetAuthority(), msg.Signer)
+ }
+
+ ctx := sdk.UnwrapSDKContext(goCtx)
+ checksum, err := k.storeWasmCode(ctx, msg.WasmByteCode, ibcwasm.GetVM().StoreCode)
+ if err != nil {
+ return nil, errorsmod.Wrap(err, "failed to store wasm bytecode")
+ }
+
+ emitStoreWasmCodeEvent(ctx, checksum)
+
+ return &types.MsgStoreCodeResponse{
+ Checksum: checksum,
+ }, nil
+}
+```
+
+The contract's bytecode is not stored in state (it is actually unnecessary and wasteful to store it, since
+the Wasm VM already stores it and can be queried back, if needed). The checksum is simply the hash of the bytecode
+of the contract and it is stored in state in an entry with key `checksums` that contains the checksums for the bytecodes that have been stored.
+
+### How light client proxy works?
+
+The light client proxy behind the scenes will call a CosmWasm smart contract instance with incoming arguments serialized
+in JSON format with appropriate environment information. Data returned by the smart contract is deserialized and
+returned to the caller.
+
+Consider the example of the `VerifyClientMessage` function of `ClientState` interface. Incoming arguments are
+packaged inside a payload object that is then JSON serialized and passed to `queryContract`, which executes `WasmVm.Query`
+and returns the slice of bytes returned by the smart contract. This data is deserialized and passed as return argument.
+
+```go
+type QueryMsg struct {
+ Status *StatusMsg `json:"status,omitempty"`
+ ExportMetadata *ExportMetadataMsg `json:"export_metadata,omitempty"`
+ TimestampAtHeight *TimestampAtHeightMsg `json:"timestamp_at_height,omitempty"`
+ VerifyClientMessage *VerifyClientMessageMsg `json:"verify_client_message,omitempty"`
+ CheckForMisbehaviour *CheckForMisbehaviourMsg `json:"check_for_misbehaviour,omitempty"`
+}
+
+type verifyClientMessageMsg struct {
+ ClientMessage *ClientMessage `json:"client_message"`
+}
+
+// VerifyClientMessage must verify a ClientMessage.
+// A ClientMessage could be a Header, Misbehaviour, or batch update.
+// It must handle each type of ClientMessage appropriately.
+// Calls to CheckForMisbehaviour, UpdateStaåte, and UpdateStateOnMisbehaviour
+// will assume that the content of the ClientMessage has been verified
+// and can be trusted. An error should be returned
+// if the ClientMessage fails to verify.
+func (cs ClientState) VerifyClientMessage(
+ ctx sdk.Context,
+ _ codec.BinaryCodec,
+ clientStore storetypes.KVStore,
+ clientMsg exported.ClientMessage
+) error {
+ clientMessage, ok := clientMsg.(*ClientMessage)
+ if !ok {
+ return errorsmod.Wrapf(ibcerrors.ErrInvalidType, "expected type: %T, got: %T", &ClientMessage{}, clientMsg)
+ }
+
+ payload := QueryMsg{
+ VerifyClientMessage: &VerifyClientMessageMsg{ClientMessage: clientMessage.Data},
+ }
+ _, err := wasmQuery[EmptyResult](ctx, clientStore, &cs, payload)
+ return err
+}
+```
+
+### Global Wasm VM variable
+
+The 08-wasm keeper structure keeps a reference to the Wasm VM instantiated in the keeper constructor function. The keeper uses
+the Wasm VM to store the bytecode of light client contracts. However, the Wasm VM is also needed in the 08-wasm implementations of
+some of the `ClientState` interface functions to initialise a contract, execute calls on the contract and query the contract. Since
+the `ClientState` functions do not have access to the 08-wasm keeper, then it has been decided to keep a global pointer variable that
+points to the same instance as the one in the 08-wasm keeper. This global pointer variable is then used in the implementations of
+the `ClientState` functions.
+
+## Consequences
+
+### Positive
+
+- Adding support for new light client or upgrading existing light client is way easier than before and only requires single transaction instead of a hard-fork.
+- Improves maintainability of ibc-go, since no change in codebase is required to support new client or upgrade it.
+- The existence of support for Rust dependencies in light clients which may not exist in Go.
+
+### Negative
+
+- Light clients written in Rust need to be written in a subset of Rust which could compile in Wasm.
+- Introspecting light client code is difficult as only compiled bytecode exists in the blockchain.
diff --git a/ibc/next/architecture/adr.template.mdx b/ibc/next/architecture/adr.template.mdx
new file mode 100644
index 00000000..d5ec7fc1
--- /dev/null
+++ b/ibc/next/architecture/adr.template.mdx
@@ -0,0 +1,38 @@
+# ADR \{ADR-NUMBER\}: \{TITLE\}
+
+## Changelog
+
+- \{date\}: \{changelog\}
+
+## Status
+
+> A decision may be "proposed" if it hasn't been agreed upon yet, or "accepted" once it is agreed upon. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or "superseded" with a reference to its replacement.
+
+\{Deprecated|Proposed|Accepted\}
+
+## Context
+
+> This section contains all the context one needs to understand the current state, and why there is a problem. It should be as succinct as possible and introduce the high-level idea behind the solution.
+
+## Decision
+
+> This section explains all of the details of the proposed solution, including implementation details.
+It should also describe affects / corollary items that may need to be changed as a part of this.
+If the proposed change will be large, please also indicate a way to do the change to maximize ease of review.
+(e.g. the optimal split of things to do between separate PR's)
+
+## Consequences
+
+> This section describes the consequences, after applying the decision. All consequences should be summarized here, not just the "positive" ones.
+
+### Positive
+
+### Negative
+
+### Neutral
+
+## References
+
+> Are there any relevant PR comments, issues that led up to this, or articles referenced for why we made the given design choice? If so link them here!
+
+- \{reference link\}
diff --git a/ibc/next/index.mdx b/ibc/next/index.mdx
deleted file mode 100644
index fad17d3c..00000000
--- a/ibc/next/index.mdx
+++ /dev/null
@@ -1,36 +0,0 @@
----
-title: "IBC Documentation"
-description: "Inter-Blockchain Communication Protocol (IBC) documentation"
----
-
-# IBC Documentation
-
-The Inter-Blockchain Communication Protocol (IBC) is a protocol for authentication and transport of data between blockchains.
-
-## Overview
-
-IBC enables secure and reliable communication between independent blockchains, allowing them to transfer tokens and other data in a trust-minimized way.
-
-### Key Features
-
-- **Interoperability** - Connect heterogeneous blockchains
-- **Security** - Cryptographic verification of cross-chain messages
-- **Composability** - Build cross-chain applications
-- **Modularity** - Extensible protocol design
-
-## Getting Started
-
-Documentation for IBC is currently being developed. For the latest information, please visit:
-
-- [IBC Protocol Specification](https://github.com/cosmos/ibc)
-- [IBC Go Implementation](https://github.com/cosmos/ibc-go)
-- [IBC Developer Resources](/ibc/next/intro)
-
-## Resources
-
-- [GitHub Repository](https://github.com/cosmos/ibc-go)
-- [Release Notes](/ibc/next/index)
-
----
-
-*This documentation is under active development.*
diff --git a/ibc/next/intro.mdx b/ibc/next/intro.mdx
index 36e305c1..7343166c 100644
--- a/ibc/next/intro.mdx
+++ b/ibc/next/intro.mdx
@@ -13,20 +13,35 @@ The protocol realizes this interoperability by specifying a set of data structur
**Notice**
-Since ibc-go v10, there are two versions of the protocol in the same release: IBC classic and IBC v2. The protocols are seperate - a connection uses either IBC classic or IBC v2
+Since ibc-go v10, there are two versions of the protocol in the same release: IBC classic and IBC v2. The protocols are separate - a connection uses either IBC classic or IBC v2
-## High-level overview of IBC v2
+## Overview of IBC v2
-For a high level overview of IBC v2, please refer to [this blog post.](https://ibcprotocol.dev/blog/ibc-v2-announcement) For a more detailed understanding of the IBC v2 protocol, please refer to the [IBC v2 protocol specification.](https://github.com/cosmos/ibc/tree/main/spec/IBC_V2)
+IBC v2 is a streamlined redesign of the IBC Classic protocol. It reduces architectural complexity and expands IBC connectivity to gas-metered environments such as the EVM. The protocol is organized around three components:
+
+- **Clients** track the consensus state of a counterparty chain and serve as the primary identifier for a connection. A packet in IBC v2 references a source client and a destination client — not a channel — and is verified by proving the packet commitment against the counterparty's stored consensus state.
+- **Router** directs packets to the correct application module by port ID. It consolidates the routing logic that was previously spread across connections, channels, and the port router in IBC Classic into a single abstraction.
+- **Applications** implement the `IBCModule` interface to handle the packet lifecycle: `OnSendPacket`, `OnRecvPacket`, `OnAcknowledgementPacket`, and `OnTimeoutPacket`. There are no channel handshake callbacks; application version and encoding are declared per-payload at send time.
+
+Packets in IBC v2 carry one or more **Payloads**. Each payload specifies the source port, destination port, application version, encoding (e.g. protobuf, JSON, or ABI), and the raw application data. A single packet can carry payloads for multiple applications simultaneously. Execution is atomic: if any payload fails, all state changes from that packet are rolled back.
+
+Notable features of IBC v2:
+
+- **Relayers** are off-chain processes that observe packet commitments on the sending chain and submit them with Merkle proofs to the receiving chain, and do the same in reverse for acknowledgements and timeouts. Relaying is permissionless by default; IBC v2 optionally allows chains to restrict relaying per client to an authorized allowlist.
+- **Client pairs are the connection primitive**: two chains communicate via a pair of light clients, one on each chain. Before packets can flow, each chain registers the other's client ID as its counterparty. This replaces the multi-step connection and channel handshakes of IBC Classic.
+- **Flexible client types**: clients are not restricted to light clients. Any verification model can be implemented — light clients, multi-sig, ZK-proof verifiers, or conditional clients. This enables cost-efficient light client verification on chains like Ethereum.
+- **No channel upgrades**: since application version and encoding are declared per-payload, applications can change their wire format without a channel upgrade coordination process. All application versions route through the same client connection.
+- **Timestamp-only timeout**: IBC v2 packets carry a single Unix timestamp timeout (in seconds). Block heights are chain-specific and don't translate across heterogeneous chains like Ethereum, so timestamps are used instead as a universal primitive. If a packet is not received before the timeout, a relayer can submit a proof of non-receipt and the sending chain times out the packet.
+
+For a detailed understanding of the protocol design, refer to the [IBC v2 specification.](/ibc/next/spec/IBC_V2/README) For a high-level introduction, see the [IBC v2 announcement blog post.](https://ibcprotocol.dev/blog/ibc-v2-announcement) If you are interested in using IBC v2 to connect Cosmos chains and Ethereum, take a look at the [IBC Eureka](/skip-go/eureka/eureka-overview) documentation.
-If you are interested in using the cannonical deployment of IBC v2, connecting Cosmos chains and Ethereum, take a look at the [IBC Eureka](/skip-go/eureka/eureka-overview) documentation to get started.
## High-level overview of IBC Classic
The following diagram shows how IBC works at a high level:
-
+
The transport layer (TAO) provides the necessary infrastructure to establish secure connections and authenticate data packets between chains. The application layer builds on top of the transport layer and defines exactly how data packets should be packaged and interpreted by the sending and receiving chains.
diff --git a/ibc/next/middleware/packet-forward-middleware/example-usage.mdx b/ibc/next/middleware/packet-forward-middleware/example-usage.mdx
index e689e1e7..94c0150e 100644
--- a/ibc/next/middleware/packet-forward-middleware/example-usage.mdx
+++ b/ibc/next/middleware/packet-forward-middleware/example-usage.mdx
@@ -61,14 +61,46 @@ sequenceDiagram
Chain B ->> Chain A: ACK error
```
+### Multi-hop Transfer A → B → C → D (full success)
+
+```mermaid
+sequenceDiagram
+ autonumber
+ Chain A ->> Chain B: PFM transfer
+ Chain B --> Chain B: recv_packet
+ Chain B ->> Chain C: forward
+ Chain C --> Chain C: recv_packet
+ Chain C ->> Chain D: forward
+ Chain D --> Chain D: recv_packet
+ Chain D ->> Chain C: ack
+ Chain C ->> Chain B: ack
+ Chain B ->> Chain A: ack
+```
+
+### Multi-hop Transfer A → B → C → D (error at D, refund to A)
+
+```mermaid
+sequenceDiagram
+ autonumber
+ Chain A ->> Chain B: PFM transfer
+ Chain B --> Chain B: recv_packet
+ Chain B ->> Chain C: forward
+ Chain C --> Chain C: recv_packet
+ Chain C ->> Chain D: forward
+ Chain D --> Chain D: recv_packet (error)
+ Chain D ->> Chain C: ACK error
+ Chain C ->> Chain B: ACK error
+ Chain B ->> Chain A: ACK error
+```
+
### A -> B -> C full success
1. `A` This sends packet over underlying ICS-004 wrapper with memo as is.
2. `B` This receives packet and parses it into ICS-020 packet.
3. `B` Validates `forward` packet on this step, return `ACK` error if fails.
4. `B` If other middleware not yet called ICS-020, call it and ACK error on fail. Tokens minted or unescrowed here.
-5. `B` Handle denom. If denom prefix is from `B`, remove it. If denom prefix is other chain - add `B` prefix.
-6. `B` Take fee, create new ICS-004 packet with timeout from forward for next step, and remaining inner `memo`.
+5. `B` Handle denom. If denom prefix matches the source chain's (counterparty, A's) port/channel, remove it (path-unwind). If not — add `B`'s port/channel as prefix.
+6. `B` Create new ICS-004 packet with timeout from forward for next step, and remaining inner `memo`.
7. `B` Send transfer to `C` with parameters obtained from `memo`. Tokens burnt or escrowed here.
8. `B` Store tracking `in flight packet` under next `(channel, port, ICS-20 transfer sequence)`, do not `ACK` packet yet.
9. `C` Handle ICS-020 packet as usual.
@@ -110,27 +142,42 @@ In the case of a timeout after 10 minutes for either forward, the packet would b
`next` as JSON
-```json expandable
+```json
{
"forward": {
- "receiver": "pfm", / intentionally invalid
+ "receiver": "pfm",
"port": "transfer",
- "channel": "channel-123",
- "timeout": "10m",
- "retries": 2,
+ "channel": "channel-123",
+ "timeout": "10m",
+ "retries": 2,
"next": {
- "forward": {
- "receiver": "chain-d-bech32-address",
- "port": "transfer",
- "channel": "channel-234",
- "timeout": "10m",
- "retries": 2
+ "forward": {
+ "receiver": "chain-d-bech32-address",
+ "port": "transfer",
+ "channel": "channel-234",
+ "timeout": "10m",
+ "retries": 2
}
}
}
}
```
+`next` as escaped JSON string
+
+```json
+{
+ "forward": {
+ "receiver": "pfm",
+ "port": "transfer",
+ "channel": "channel-123",
+ "timeout": "10m",
+ "retries": 2,
+ "next": "{\"forward\":{\"receiver\":\"chain-d-bech32-address\",\"port\":\"transfer\",\"channel\":\"channel-234\",\"timeout\":\"10m\",\"retries\":2}}"
+ }
+}
+```
+
## Intermediate Address Security
Intermediate chains don’t need a valid receiver address. Instead, they derive a secure address from the packet’s sender and channel, preventing users from forwarding tokens to arbitrary accounts.
diff --git a/ibc/next/middleware/packet-forward-middleware/integration.mdx b/ibc/next/middleware/packet-forward-middleware/integration.mdx
index e8b01d21..76bf48f3 100644
--- a/ibc/next/middleware/packet-forward-middleware/integration.mdx
+++ b/ibc/next/middleware/packet-forward-middleware/integration.mdx
@@ -2,56 +2,53 @@
title: Integration
description: >-
This document provides instructions on integrating and configuring the Packet
- Forward Middleware (PFM) within your existing chain implementation. The
- integration steps include the following:
+ Forward Middleware (PFM) within your existing chain implementation.
---
This document provides instructions on integrating and configuring the Packet Forward Middleware (PFM) within your
existing chain implementation.
The integration steps include the following:
-1. [Import the PFM, initialize the PFM Module & Keeper, initialize the store keys and module params, and initialize the Begin/End Block logic and InitGenesis order](#example-integration-of-the-packet-forward-middleware)
+1. [Import the PFM, initialize the PFM Module & Keeper, initialize the store keys, and initialize the Begin/End Block logic and InitGenesis order](#example-integration-of-the-packet-forward-middleware)
2. [Configure the IBC application stack including the transfer module](#configuring-the-transfer-application-stack-with-packet-forward-middleware)
-3. [Configuration of additional options such as timeout period, number of retries on timeout, refund timeout period, and fee percentage](#configurable-options-in-the-packet-forward-middleware)
+3. [Configuration of additional options such as timeout period and number of retries on timeout](#configurable-options-in-the-packet-forward-middleware)
Integration of the PFM should take approximately 20 minutes.
## Example integration of the Packet Forward Middleware
-```go expandable
-/ app.go
+```go
+// app.go
-/ Import the packet forward middleware
+// Import the packet forward middleware
import (
-
- "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v10/packetforward"
- packetforwardkeeper "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v10/packetforward/keeper"
- packetforwardtypes "github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v10/packetforward/types"
+ packetforward "github.com/cosmos/ibc-go/v10/modules/apps/packet-forward-middleware"
+ packetforwardkeeper "github.com/cosmos/ibc-go/v10/modules/apps/packet-forward-middleware/keeper"
+ packetforwardtypes "github.com/cosmos/ibc-go/v10/modules/apps/packet-forward-middleware/types"
)
...
-/ Register the AppModule for the packet forward middleware module
+// Register the AppModule for the packet forward middleware module
ModuleBasics = module.NewBasicManager(
...
- packetforward.AppModuleBasic{
-},
+ packetforward.AppModuleBasic{},
...
)
...
-/ Add packet forward middleware Keeper
+// Add packet forward middleware Keeper
type App struct {
- ...
- PacketForwardKeeper *packetforwardkeeper.Keeper
- ...
+ ...
+ PFMKeeper *packetforwardkeeper.Keeper
+ ...
}
...
-/ Create store keys
- keys := sdk.NewKVStoreKeys(
+// Create store keys
+keys := sdk.NewKVStoreKeys(
...
packetforwardtypes.StoreKey,
...
@@ -59,75 +56,52 @@ type App struct {
...
-/ Initialize the packet forward middleware Keeper
-/ It's important to note that the PFM Keeper must be initialized before the Transfer Keeper
-app.PacketForwardKeeper = packetforwardkeeper.NewKeeper(
- appCodec,
- keys[packetforwardtypes.StoreKey],
- nil, / will be zero-value here, reference is set later on with SetTransferKeeper.
- app.IBCKeeper.ChannelKeeper,
- appKeepers.DistrKeeper,
- app.BankKeeper,
- app.IBCKeeper.ChannelKeeper,
- authtypes.NewModuleAddress(govtypes.ModuleName).String(),
-)
+// Initialize the transfer module Keeper first (PFM Keeper requires it)
+app.TransferKeeper = ibctransferkeeper.NewKeeper(...)
-/ Initialize the transfer module Keeper
-app.TransferKeeper = ibctransferkeeper.NewKeeper(
+// Initialize the packet forward middleware Keeper
+app.PFMKeeper = packetforwardkeeper.NewKeeper(
appCodec,
- keys[ibctransfertypes.StoreKey],
- app.GetSubspace(ibctransfertypes.ModuleName),
- app.PacketForwardKeeper,
+ app.AccountKeeper.AddressCodec(),
+ runtime.NewKVStoreService(keys[packetforwardtypes.StoreKey]),
+ app.TransferKeeper,
app.IBCKeeper.ChannelKeeper,
- &app.IBCKeeper.PortKeeper,
- app.AccountKeeper,
app.BankKeeper,
- scopedTransferKeeper,
+ authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
-app.PacketForwardKeeper.SetTransferKeeper(app.TransferKeeper)
-
-/ See the section below for configuring an application stack with the packet forward middleware
+// See the section below for configuring an application stack with the packet forward middleware
...
-/ Register packet forward middleware AppModule
+// Register packet forward middleware AppModule
app.moduleManager = module.NewManager(
...
- packetforward.NewAppModule(app.PacketForwardKeeper, app.GetSubspace(packetforwardtypes.ModuleName)),
+ packetforward.NewAppModule(app.PFMKeeper),
)
...
-/ Add packet forward middleware to begin blocker logic
+// Add packet forward middleware to begin blocker logic
app.moduleManager.SetOrderBeginBlockers(
...
packetforwardtypes.ModuleName,
...
)
-/ Add packet forward middleware to end blocker logic
+// Add packet forward middleware to end blocker logic
app.moduleManager.SetOrderEndBlockers(
...
packetforwardtypes.ModuleName,
...
)
-/ Add packet forward middleware to init genesis logic
+// Add packet forward middleware to init genesis logic
app.moduleManager.SetOrderInitGenesis(
...
packetforwardtypes.ModuleName,
...
)
-
-/ Add packet forward middleware to init params keeper
-func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey)
-
-paramskeeper.Keeper {
- ...
- paramsKeeper.Subspace(packetforwardtypes.ModuleName).WithKeyTable(packetforwardtypes.ParamKeyTable())
- ...
-}
```
## Configuring the transfer application stack with Packet Forward Middleware
@@ -136,46 +110,38 @@ Here is an example of how to create an application stack using `transfer` and `p
The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`.
The in-line comments describe the execution flow of packets between the application stack and IBC core.
-For more information on configuring an IBC application stack see the ibc-go docs [here](https://github.com/cosmos/ibc-go/blob/e69a833de764fa0f5bdf0338d9452fd6e579a675/docs/docs/04-middleware/01-ics29-fee/02-integration.md#configuring-an-application-stack-with-fee-middleware).
+For more information on configuring an IBC application stack see the [middleware development guide](/ibc/next/ibc/middleware/develop).
-```go expandable
-/ Create Transfer Stack
-/ SendPacket, since it is originating from the application to core IBC:
-/ transferKeeper.SendPacket -> packetforward.SendPacket -> channel.SendPacket
+```go
+// Create Transfer Stack
+// SendPacket, since it is originating from the application to core IBC:
+// transferKeeper.SendPacket -> packetforward.SendPacket -> channel.SendPacket
-/ RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
-/ channel.RecvPacket -> packetforward.OnRecvPacket -> transfer.OnRecvPacket
+// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
+// channel.RecvPacket -> packetforward.OnRecvPacket -> transfer.OnRecvPacket
-/ transfer stack contains (from top to bottom):
-/ - Packet Forward Middleware
-/ - Transfer
-var transferStack ibcporttypes.IBCModule
-transferStack = transfer.NewIBCModule(app.TransferKeeper)
+// transfer stack contains (from top to bottom):
+// - Packet Forward Middleware
+// - Transfer
-transferStack = packetforward.NewIBCMiddleware(
- transferStack,
- app.PacketForwardKeeper,
- 0, / retries on timeout
- packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, / forward timeout
-)
+// create IBC module from bottom to top of stack
+transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper)
+transferStack.Base(transfer.NewIBCModule(app.TransferKeeper)).
+ Next(packetforward.NewIBCMiddleware(
+ app.PFMKeeper,
+ 0, // retries on timeout
+ packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp, // forward timeout
+ ))
-/ Add transfer stack to IBC Router
-ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack)
+// Add transfer stack to IBC Router
+ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build())
```
## Configurable options in the Packet Forward Middleware
The Packet Forward Middleware has several configurable options available when initializing the IBC application stack.
You can see these passed in as arguments to `packetforward.NewIBCMiddleware` and they include the number of retries that
-will be performed on a forward timeout, the timeout period that will be used for a forward, and the timeout period that
-will be used for performing refunds in the case that a forward is taking too long.
-
-Additionally, there is a fee percentage parameter that can be set in `InitGenesis`, this is an optional parameter that
-can be used to take a fee from each forwarded packet which will then be distributed to the community pool. In the
-`OnRecvPacket` callback `ForwardTransferPacket` is invoked which will attempt to subtract a fee from the forwarded
-packet amount if the fee percentage is non-zero.
+will be performed on a forward timeout, and the timeout period that will be used for a forward.
* Retries On Timeout - how many times will a forward be re-attempted in the case of a timeout.
* Timeout Period - how long can a forward be in progress before giving up.
-* Refund Timeout - how long can a forward be in progress before issuing a refund back to the original source chain.
-* Fee Percentage - % of the forwarded packet amount which will be subtracted and distributed to the community pool.
diff --git a/ibc/next/middleware/packet-forward-middleware/overview.mdx b/ibc/next/middleware/packet-forward-middleware/overview.mdx
index 482fc16d..3fec48b7 100644
--- a/ibc/next/middleware/packet-forward-middleware/overview.mdx
+++ b/ibc/next/middleware/packet-forward-middleware/overview.mdx
@@ -1,5 +1,6 @@
---
title: Overview
+description: Packet Forward Middleware enables multi-hop IBC token transfers through intermediate chains.
---
@@ -15,15 +16,13 @@ Packet Forward Middleware enables multi-hop token transfers by forwarding IBC pa
* **Path-Unwinding Functionality:**
Because the fungibility of tokens transferred between chains is determined by [the path the tokens have travelled](/ibc/next/apps/transfer/overview#denomination-trace), i.e. the same token sent from chain A to chain B is not fungible with the same token sent from chain A, to chain C and then to chain B, packet forward middleware also enables routing tokens back through their source, before sending onto the final destination.
* **Asynchronous Acknowledgements:**
- Acknowledgements are only written to the origin chain after all forwarding steps succeed or fail, users only need to monitor the source chain for the result.
+ Multi-hop sequences are atomic: acknowledgements are only written to the origin chain after all forwarding steps succeed or fail, so users only need to monitor the source chain for the result.
* **Retry and Timeout Handling:**
The middleware can be configured to retry forwarding in the case that there was a timeout.
* **Forwarding across multiple chains with nested memos:**
- Instructions on which route to take to forward a packet across more than one chain can be set within a nested JSON with the memo field
-* **Configurable Fee Deduction on Recieve:**
- Integrators of PFM can choose to deduct a percentage of tokens forwarded through their chain and distribute these tokens to the community pool.
+ Instructions on which route to take to forward a packet across more than one chain can be set within a nested JSON with the memo field.
-## How it works?
+## How it works
1. User initiates a `MsgTransfer` with a memo JSON payload containing forwarding instructions.
diff --git a/ibc/next/middleware/rate-limit-middleware/integration.mdx b/ibc/next/middleware/rate-limit-middleware/integration.mdx
index c97f41be..a3117b86 100644
--- a/ibc/next/middleware/rate-limit-middleware/integration.mdx
+++ b/ibc/next/middleware/rate-limit-middleware/integration.mdx
@@ -1,8 +1,148 @@
---
title: Integration
-description: >-
- This section should be completed once the middleware wiring approach is
- finalised.
+description: Integrate and configure Rate
+ Limit Middleware.
---
-This section should be completed once the middleware wiring approach is finalised.
+This document provides instructions on integrating and configuring the Rate Limit Middleware within your existing chain implementation.
+
+## Example integration of the Rate Limit Middleware
+
+```go
+// app.go
+
+// Import the rate limit middleware
+import (
+ ratelimiting "github.com/cosmos/ibc-go/v10/modules/apps/rate-limiting"
+ ratelimitkeeper "github.com/cosmos/ibc-go/v10/modules/apps/rate-limiting/keeper"
+ ratelimittypes "github.com/cosmos/ibc-go/v10/modules/apps/rate-limiting/types"
+)
+
+...
+
+// Register the AppModule for the rate limiting module
+ModuleBasics = module.NewBasicManager(
+ ...
+ ratelimiting.AppModuleBasic{},
+ ...
+)
+
+...
+
+// Add rate limiting Keeper
+type App struct {
+ ...
+ RateLimitKeeper *ratelimitkeeper.Keeper
+ ...
+}
+
+...
+
+// Create store keys
+keys := sdk.NewKVStoreKeys(
+ ...
+ ratelimittypes.StoreKey,
+ ...
+)
+
+...
+
+// Initialize the rate limit middleware Keeper
+app.RateLimitKeeper = ratelimitkeeper.NewKeeper(
+ appCodec,
+ app.AccountKeeper.AddressCodec(),
+ runtime.NewKVStoreService(keys[ratelimittypes.StoreKey]),
+ app.IBCKeeper.ChannelKeeper,
+ app.IBCKeeper.ClientKeeper,
+ app.BankKeeper,
+ authtypes.NewModuleAddress(govtypes.ModuleName).String(),
+)
+
+// See the section below for configuring an application stack with the rate limit middleware
+
+...
+
+// Register rate limiting AppModule
+app.moduleManager = module.NewManager(
+ ...
+ ratelimiting.NewAppModule(app.RateLimitKeeper),
+)
+
+...
+
+// Add rate limiting to begin blocker logic (required for periodic rate limit resets)
+app.moduleManager.SetOrderBeginBlockers(
+ ...
+ ratelimittypes.ModuleName,
+ ...
+)
+
+// Add rate limiting to end blocker logic
+app.moduleManager.SetOrderEndBlockers(
+ ...
+ ratelimittypes.ModuleName,
+ ...
+)
+
+// Add rate limiting to init genesis logic
+app.moduleManager.SetOrderInitGenesis(
+ ...
+ ratelimittypes.ModuleName,
+ ...
+)
+```
+
+## Configuring the transfer application stack with Rate Limit Middleware
+
+Here is an example of how to create an application stack using `transfer` and `rate-limiting`.
+The following `transferStack` is configured in `app/app.go` and added to the IBC `Router`.
+The in-line comments describe the execution flow of packets between the application stack and IBC core.
+
+For more information on configuring an IBC application stack see the [middleware development guide](/ibc/next/ibc/middleware/develop).
+
+```go
+// Create Transfer Stack
+// SendPacket, since it is originating from the application to core IBC:
+// transferKeeper.SendPacket -> ratelimit.SendPacket -> channel.SendPacket
+
+// RecvPacket, message that originates from core IBC and goes down to app, the flow is the other way
+// channel.RecvPacket -> ratelimit.OnRecvPacket -> transfer.OnRecvPacket
+
+// transfer stack contains (from top to bottom):
+// - Rate Limit Middleware
+// - Transfer
+
+// create IBC module from bottom to top of stack
+transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper)
+transferStack.Base(transfer.NewIBCModule(app.TransferKeeper)).
+ Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper))
+
+// Add transfer stack to IBC Router
+ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build())
+```
+
+## Using Rate Limit Middleware with Packet Forward Middleware
+
+If both PFM and Rate Limit Middleware are integrated, they can be stacked together. In the reference implementation, Rate Limit Middleware is placed above PFM so that rate limits are enforced on the final inbound transfer before forwarding:
+
+```go
+// transfer stack contains (from top to bottom):
+// - Rate Limit Middleware
+// - Packet Forward Middleware
+// - Transfer
+
+// SendPacket flow: transferKeeper.SendPacket -> PFM.SendPacket -> RateLimit.SendPacket -> channel.SendPacket
+// RecvPacket flow: channel.RecvPacket -> RateLimit.OnRecvPacket -> PFM.OnRecvPacket -> transfer.OnRecvPacket
+
+// create IBC module from bottom to top of stack
+transferStack := porttypes.NewIBCStackBuilder(app.IBCKeeper.ChannelKeeper)
+transferStack.Base(transfer.NewIBCModule(app.TransferKeeper)).
+ Next(packetforward.NewIBCMiddleware(
+ app.PFMKeeper,
+ 0, // retries on timeout
+ packetforwardkeeper.DefaultForwardTransferPacketTimeoutTimestamp,
+ )).
+ Next(ratelimiting.NewIBCMiddleware(app.RateLimitKeeper))
+
+ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferStack.Build())
+```
diff --git a/ibc/next/middleware/rate-limit-middleware/overview.mdx b/ibc/next/middleware/rate-limit-middleware/overview.mdx
index f88ae6a3..f876a2cb 100644
--- a/ibc/next/middleware/rate-limit-middleware/overview.mdx
+++ b/ibc/next/middleware/rate-limit-middleware/overview.mdx
@@ -1,28 +1,25 @@
---
title: Overview
-description: >-
- Learn about rate limit middleware, a middleware that can be used in
- combination with token transfers (ICS-20) to control the amount of in and
- outflows of assets in a certain time period.
+description: About Rate Limit Middleware
---
-Learn about rate limit middleware, a middleware that can be used in combination with token transfers (ICS-20) to control the amount of in and outflows of assets in a certain time period.
+Learn about Rate Limit Middleware, a middleware that can be used in combination with token transfers (ICS-20) to control the amount of in and outflows of assets in a certain time period.
## What is Rate Limit Middleware?
The rate limit middleware enforces rate limits on IBC token transfers coming into and out of a chain. It supports:
* **Risk Mitigation:** In case of a bug exploit, attack or economic failure of a connected chain, it limits the impact to the in/outflow specified for a given time period.
-* **Token Filtering:** Through the use of a blacklist, the middleware can completely block tokens entering or leaving a domain, relevant for complicance or giving asset issuers greater control over the domains token can be sent to.
-* **Uninterupted Packet Flow:** When desired, rate limits can be bypassed by using the whitelist, to avoid any restriction on asset in or outflows.
+* **Token Filtering:** Through the use of a blacklist, the middleware can completely block tokens entering or leaving a domain, relevant for compliance or giving asset issuers greater control over the domains token can be sent to.
+* **Uninterrupted Packet Flow:** When desired, rate limits can be bypassed by using the whitelist, to avoid any restriction on asset in or outflows.
## How it works
The rate limiting middleware determines whether tokens can flow into or out of a chain. The middleware does this by:
-1. Check transfer limits for an asset (Quota): When tokens are recieved or sent, the middleware determines whether the amount of tokens flowing in or out have exceeded the limit.
+1. Check transfer limits for an asset (Quota): When tokens are received or sent, the middleware determines whether the amount of tokens flowing in or out have exceeded the limit.
-2. Track in or outflow: When tokens enter or leave the chain, the amount transferred is tracked in state
+2. Track in or outflow: When tokens enter or leave the chain, the amount transferred is tracked in state.
3. Block or allow token flow: Dependent on the limit, the middleware will either allow the tokens to pass through or block the tokens.
diff --git a/ibc/next/middleware/rate-limit-middleware/setting-limits.mdx b/ibc/next/middleware/rate-limit-middleware/setting-limits.mdx
index 4cc0c79d..704e6af8 100644
--- a/ibc/next/middleware/rate-limit-middleware/setting-limits.mdx
+++ b/ibc/next/middleware/rate-limit-middleware/setting-limits.mdx
@@ -6,16 +6,16 @@ description: >-
must be executed which includes:
---
-Rate limits are set through a governance-gated authority on a per denom, and per channel / client basis. To add a rate limit, the [`MsgAddRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L26-L34) message must be executed which includes:
+Rate limits are set through a governance-gated authority on a per denom, and per channel / client basis. To add a rate limit, the [`MsgAddRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L27-L35) message must be executed which includes:
* Denom: the asset that the rate limit should be applied to
* ChannelOrClientId: the channelID for use with IBC classic connections, or the clientID for use with IBC v2 connections
-* MaxPercentSend: the outflow threshold as a percentage of the `channelValue`. More explicitly, a packet being sent would exceed the threshold quota if: (Outflow - Inflow + Packet Amount) / channelValue is greater than MaxPercentSend
+* MaxPercentSend: the outflow threshold as a percentage of the `channelValue`. More explicitly, a packet being sent would exceed the threshold quota if: (Outflow - Inflow + Packet Amount) / channelValue is greater than MaxPercentSend / 100
* MaxPercentRecv: the inflow threshold as a percentage of the `channelValue`
* DurationHours: the length of time, after which the rate limits reset
## Updating, Removing or Resetting Rate Limits
* If rate limits were set to be too low or high for a given channel/client, they can be updated with [`MsgUpdateRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L81-L89).
-* If rate limits are no longer needed, they can be removed with [`MsgRemoveRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L136-L141).
-* If the flow counter needs to be resent for a given rate limit, it is possible to do so with [`MsgResetRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L169-L174).
+* If rate limits are no longer needed, they can be removed with [`MsgRemoveRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L135-L140).
+* If the flow counter needs to be reset for a given rate limit, it is possible to do so with [`MsgResetRateLimit`](https://github.com/cosmos/ibc-go/blob/main/modules/apps/rate-limiting/types/msgs.go#L167-L172).
diff --git a/ibc/next/spec/IBC_V2/README.mdx b/ibc/next/spec/IBC_V2/README.mdx
new file mode 100644
index 00000000..a5e20276
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/README.mdx
@@ -0,0 +1,204 @@
+---
+title: IBC v2
+stage: experimental
+version_compatibility:
+ - ibc-go v10.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchainlabs.io
+created: 2024-08-15
+---
+
+### Introduction
+
+IBC v2 is an end-to-end protocol for reliable, authenticated communication between modules on separate distributed ledgers. IBC makes NO assumptions about the consensus algorithm or the state machine. So long as the distributed ledger satisfies the minimal requirements in [ICS-24 Host Requirements](../core/ics-024-host-requirements/README), it can support the IBC v2 protocol and communicate across any application in the IBC v2 network.
+
+The IBC v2 protocol can be conceptualized in three distinct layers: **IBC CLIENTS**, **IBC CORE**, and **IBC APPS**. **IBC APPS** are the modules that wish to communicate with each other across different ledgers in the IBC v2 network. On example is ICS-20 fungible token transfer which facilitates sending tokens securely from one ledger to another by sending token packet data using **IBC CORE** and executing escrow/mint logic upon sending/receiving the ICS-20 token packet data from counterparty ICS20 applications.
+**IBC CLIENTS** identifies and verifies the state of the counterparty ledger. An **IBC CLIENT** is responsible for tracking updates to the state machine and verifying state against a given update.
+**IBC CORE** is the handler that implements the transport, authentication, and ordering semantics (hereafter `IBC/TAO`), it **uses** the **IBC CLIENT** to authenticate the packet and then sends the application packet data to the **IBC APP** which will handle the application data. Thus, each layer has a specific isolated responsibility. The **IBC CLIENT** only needs to verify key/value proofs of the counterparty state. The **IBC APP** only needs to process application data coming from a counterparty application. **IBC CORE** enables authenticated IBC packet flow of `SendPacket`, `RecvPacket`, `AcknowledgePacket`, `TimeoutPacket` for the **IBC APP** using the **IBC CLIENT** as a verification oracle.
+
+The goal of this document is to provide a basic overview of the IBC V2 protocol. Where appropriate, distinctions from IBC v1 will be highlighted. For a detailed specification of each layer please refer to the ICS-standards.
+
+### Specification
+
+### IBC Clients
+
+The IBC Client keeps track of counterparty state updates and exposes a verifier of the counterparty state to **IBC CORE**. An IBC client implementation achieves this through the use of two distinct structures: the `ClientState` and the `ConsensusState`. From the perspective of IBC, these are opaque bytes and are defined by the specific light client implementation. The `ClientState` is intended to encapsulate parameters of the counterparty consensus that SHOULD NOT change across heights, this can include a chain identifier and security parameters like a staking unbonding period. The `ConsensusState` on the other hand is a **view** or a snapshot of the counterparty consensus at a particular height. This **view** in almost all cases will be a highly compressed view of the counterparty consensus. The **IBC CLIENT** will not store the entire state of the counterparty chain, nor will it execute all transactions of the counterparty chain as this would be equivalent to hosting a full node. A common pattern is to have the counterparty Consensus to create a Merklized commitment of the counterparty state on each state update. The **IBC CLIENT** can add the merkle root hash to the `ConsensusState` and then verify membership/nonmembership proofs against the root hash in the stored consensus state.
+
+The **IBC CLIENT** encapsulates a particular security model, this can be anything from a multisign bridge committee to a fully verified light client of the counterparty consensus algorithm. It is up to users sending packets on the client to decide whether the security model is acceptable to them or not.
+The IBC client is responsible for taking an initial view of the counterparty consensus, and updating that view from this trusted point using the security model instantiated in the client. This initial counterparty consensus is trusted axiomatically. Thus, IBC does not have any **in-protocol** awareness of which chain a particular client is verifying. A user can inspect a client and verify for themselves that the client is tracking the chain that they care about (i.e. validating a specific consensus state matches the consensus output of the desired counterparty chain) and that the parameters of the security model encoded in the `ClientState` are satisfactory. A user need only verify the client once, either by themselves or through social consensus, once that initial trust is established; the **IBC CLIENT** MUST continue updating the view of the counterparty state from previously trusted views given the parameterized security model. Thus, once a user trusts a light client they can be guaranteed that the trust will not be violated by the client.
+
+If the security model is violated by counterparty consensus, the **IBC CLIENT** implementation MUST provide the ability to freeze the client and prevent further updates and verification. The evidence for this violation is called `Misbehaviour` and upon verification the client is frozen and all packet processing against the client is paused. Any damage already done cannot be automatically reverted in-protocol, however this mechanism ensures the attack is stopped as soon as possible so that an out-of-band recovery mechanism can intervene (e.g. governance). This recovery mechanism should ensure the consensus violation is corrected on the counterparty and any invalid state is reverted to the extent possible before resuming packet processing by unfreezing the client.
+
+The **IBC CLIENT** **must** have external endpoints for relayers (off-chain processes that have full-node access to other chains in the network) to initialize a client, update the client, and submit misbehaviour.
+
+The implementation of each of these endpoints will be specific to the particular consensus mechanism targeted. The choice of consensus algorithm itself is arbitrary, it may be a Proof-of-Stake algorithm like CometBFT, or a multisig of trusted authorities, or a rollup that relies on an additional underlying client in order to verify its consensus. However, a light client must have the ability to define finality for a given snapshot of the state machine, this may be either through single-slot finality or a finality gadget.
+
+Thus, the endpoints themselves should accept arbitrary bytes for the arguments passed into these client endpoints as it is up to each individual client implementation to unmarshal these bytes into the structures they expect.
+
+```typescript
+// initializes client with a starting client state containing all light client parameters
+// and an initial consensus state that will act as a trusted seed from which to verify future headers
+function createClient(
+ clientState: bytes,
+ consensusState: bytes,
+): (id: bytes, err: error)
+
+// once a client has been created, it can be referenced with the identifier and passed the header
+// to keep the client up-to-date. In most cases, this will cause a new consensus state derived from the header
+// to be stored in the client
+function updateClient(
+ clientId: bytes,
+ header: bytes,
+): error
+
+// once a client has been created, relayers can submit misbehaviour that proves the counterparty chain violated the trust model.
+// The light client must verify the misbehaviour using the trust model of the consensus mechanism
+// and execute some custom logic such as freezing the client from accepting future updates and proof verification.
+function submitMisbehaviour(
+ clientId: bytes,
+ misbehaviour: bytes,
+): error
+```
+
+As relayers keep the client up-to-date and add `ConsensusState`s to the client, **IBC CORE** will use the exposed verification endpoints: `VerifyMembership` and `VerifyNonMembership` to verify incoming packet-flow messages coming from the counterparty.
+
+```typescript
+// verifies a membership of a path and value in the counterparty chain identified by the provided clientId
+// against a particular ConsensusState identified by the provided height
+function verifyMembership(
+ clientId: bytes,
+ height: Number,
+ proof: bytes,
+ path: CommitmentPath,
+ value: bytes
+): error
+
+// verifies the nonmembership of a path in the counterparty chain identified by the provided clientId
+// against a particular ConsensusState identified by the provided height
+function verifyNonMembership(
+ cliendId: bytes,
+ height: Number,
+ proof: bytes,
+ path: CommitmentPath
+): error
+```
+
+### Core IBC Functionality
+
+IBC in its essence is the ability for applications on different blockchains with different consensus mechanisms to communicate with each other through client backed security. Thus, IBC needs the client described above and the IBC applications that define the packet data they wish to send and receive.
+
+In addition to these layers, IBC v1 introduced the connection and channel abstractions to connect these two fundamental layers. IBC v2 intends to compress only the necessary aspects of connection and channel layers into a single packet handler with no handshakes but before doing this it is critical to understand what service they currently provide.
+
+Properties of IBC v1 Connection:
+
+- Verifies the validity of the counterparty client
+- Establishes a unique identifier on each side for a shared abstract understanding (the connection)
+- Establishes an agreement on the IBC version and supported features
+- Allows multiple connections to be built against the same client pair
+- Establishes the delay period so this security parameter can be instantiated differently for different connections against the same client pairing.
+- Defines which channel orderings are supported
+
+Properties of IBC v1 Channel:
+
+- Separates applications into dedicated 1-1 communication channels. This prevents applications from writing into each other's channels.
+- Allows applications to come to agreement on the application parameters (version negotiation). Ensures that each side can understand the other's communication and that they are running mutually compatible logic. This version negotiation is a multi-step process that allows the finalized version to differ substantially from the one initially proposed
+- Establishes the ordering of the channel
+- Establishes unique identifiers for the applications on either chain to use to reference each other when sending and receiving packets.
+- The application protocol can be continually upgraded over time by using the upgrade handshake which allows the same channel which may have accumulated state to use new mutually agreed upon application packet data format(s) and associated new logic.
+- Ensures exactly-once delivery of packet flow datagrams (Send, Receive, Acknowledge, Timeout)
+- Ensures valid packet flow (Send => Receive => Acknowledge) XOR (Send => Timeout)
+
+### Identifying Counterparties
+
+In core IBC, the connection and channel handshakes serve to ensure the validity of counterparty clients, ensure the IBC and application versions are mutually compatible, as well as providing unique identifiers for each side to refer to the counterparty.
+
+Since we are removing handshakes in IBC V2, we must have a different way to provide the chain with knowledge of the counterparty. With a client, we can prove any key/value path on the counterparty. However, without knowing which identifier the counterparty uses when it sends messages to us; we cannot differentiate between messages sent from the counterparty to our chain vs messages sent from the counterparty with other chains. Most implementations will not be able to store the ICS-24 paths directly as a key in the global namespace; but will instead write to a reserved, prefixed keyspace so as not to conflict with other application state writes. Thus the counterparty information we must have includes both its identifier for our chain as well as the key prefix under which it will write the provable ICS-24 paths.
+
+Thus, IBC V2 will introduce a new message RegisterCounterparty that will associate the counterparty client of our chain with our client of the counterparty. Thus, if the RegisterCounterparty message is submitted to both sides correctly. Then both sides have mirrored <client,client> pairs that can be treated as identifiers for the sender and receiver chains the packet is associated with. Assuming they are correct, the client on each side is unique and provides an authenticated stream of packet data between the two chains. If the RegisterCounterparty message submits the wrong clientID, this can lead to invalid behaviour; but this is equivalent to a relayer submitting an invalid client in place of a correct client for the desired chain. In the simplest case, we can rely on out-of-band social consensus to only send on valid <client, client> pairs that represent a connection between the desired chains of the user; just as we rely on out-of-band social consensus that a given clientID and channel built on top of it is the valid, canonical identifier of our desired chain in IBC V1.
+
+
+```typescript
+interface Counterparty {
+ clientId: bytes
+ counterpartyPrefix: []bytes
+}
+```
+
+This `Counterparty` will be keyed on the client identifier existing on our chain. Thus, both sides get access to each other's client identifier. This effectively creates a connection with unique identifiers on both sides that reference each other's consensus. Thus, the resulting `client, client` pairing in IBC V2 replaces the separate connection layer that existed in IBC V1.
+
+The `RegisterCounterparty` method allows for authentication that implementations may verify before storing the provided counterparty identifier. The strongest authentication possible is to have a valid clientState and consensus state of our chain in the authentication along with a proof it was stored at the claimed counterparty identifier. This is equivalent to the `validateSelfClient` logic performed in the connection handshake.
+A simpler but weaker authentication would simply be to check that the `RegisterCounterparty` message is sent by the same relayer that initialized the client. This would make the client parameters completely initialized by the relayer. Thus, users must verify that the client is pointing to the correct chain and that the counterparty identifier is correct as well before using identifiers to send a packet. In practice, this is verified by social consensus.
+
+### IBC V2 Packet Processing
+
+IBC V2 will simply provide packet delivery between two chains communicating and identifying each other by on-chain light clients as specified in [ICS-02](../core/ics-002-client-semantics/README) with application packet data being routed to their specific IBC applications with packet-flow semantics as specified in [ICS-04](../core/ics-004-channel-and-packet-semantics/README). The packet clientIDs as mentioned above will tell the IBC router which chain to send the packets to and which chain a received packet came from, while the portIDs in the payload specifies which application on the router the packet should be sent to.
+
+Thus, once two chains have set up clients for each other with specific Identifiers, they can send IBC packets like so.
+
+```typescript
+interface Packet {
+ sequence: uint64
+ timeoutTimestamp: uint64
+ sourceClientId: Identifier // identifier of the destination client on sender chain
+ destClientId: Identifier // identifier of the sender client on the destination chain
+ payload: []Payload
+}
+```
+
+Since the packets are addressed **directly** with the underlying light clients, there are **no** more handshakes necessary. Instead the packet sender must be capable of providing the correct `` pair.
+
+Sending a packet with the wrong source client is equivalent to sending a packet with the wrong source channel. Sending a packet on a client with the wrong provided counterparty is an error and will cause the packet to be rejected. If the counterparty is set incorrectly for the new client, this is a misconfiguration in the IBC V2 setup process. Unexpected behavior may occur in this case, though it is expected that users validate the counterparty configurations on both sides are correct before sending packets using the client identifiers. This validation may be done directly or through social consensus.
+
+If the client and counterparty identifiers are setup correctly, then the correctness and soundness properties of IBC holds. IBC packet flow is guaranteed to succeed. If the counterparty is misconfigured, then as we will see it will be impossible for the intended destination to correctly verify the packet thus, the packet will simply time out.
+
+The Payload contains all the application specific information. This includes the opaque application data that the sender application wishes to send to the receiving application; it also includes the `Encoding` and `Version` that should be used to decode and process the application data. Note that this is a departure from IBC V1 where this metadata about how to process the application data was negotiated in the channel handshake. Here, each packet carries the information about how its individual data should be processed. This allows the `Version` and `Encoding` to change from packet to packet; allowing applications to upgrade asynchronously and optimistically send new packet encodings and versions to their counterparties. If the counterparty application can support receiving the new payload, it will successfully be processed; otherwise the receive will simply error and the sending application reverts state upon receiving the `ErrorAcknowledgement`. This increases the possibility for errors to occur during an application's packet processing but massively increases the flexibility of IBC applications to upgrade and evolve over time. Similarly, the portIDs on the sender and receiver application are no longer prenegotiated in the channel handshake and instead are in the payload. Thus in IBC v2; a sending application can route its packet to ANY OTHER application on the receiving application by simply specifying its portID in the payload as a receiver. It is incumbent on applications to restrict which counterparty applications it wishes to communicate with by validating the source and destination portIDs provided in the payload. Thus, the per-packet `Payload` replaces the separate channel layer that existed in IBC V1.
+
+For more details on the Payload structure, see [ICS-04](../core/ics-004-channel-and-packet-semantics/README).
+
+### Registering IBC applications on the router
+
+**IBC CORE** contains routers mapping reserved application portIDs to individual IBC applications as well as a mapping from clientIDs to individual IBC clients.
+
+```typescript
+type IBCRouter struct {
+ apps: portID -> IBCApp
+ clients: clientId -> IBCClient
+}
+```
+
+### Packet Flow
+
+For a detailed specification of the packet flow, please refer to [ICS-04](../core/ics-004-channel-and-packet-semantics/README).
+
+The packet-flow messages defined by IBC are: `SendPacket`, `ReceivePacket`, `AcknowledgePacket` and `TimeoutPacket`. `SendPacket` will most often be triggered by user-action that wants to initiate a cross-chain action (e.g. token transfer) by sending a packet from an application on the sender chain to an application on the destination chain. Every other message is the result of counterparty action, thus they must be submitted by an off-chain relayer that can submit a proof of counterparty that authenticates the message is valid. For example, the `RecvPacket` message can only be submitted if the relayer can prove that the counterparty did send a packet to our chain by submitting a proof to our on-chain client. The source chain commits the packet under the ICS-04 standardized commitment path which is constructed with `packet.sourceClientId` and `packet.sequence`. Since the `packet.sourceClientId` is a unique reference to the destination chain on the source chain, a packet commitment stored on this path is guaranteed to be a packet the source chain intends to send to the destination chain. The destination chain can verify this path using its on-chain client identified by `packet.destClientId`
+
+Similarly, `AcknowledgePacket` and `TimeoutPacket` are messages that get sent back to the sending chain after the an attempted packet receipt. If the packet receipt is successful, an application-specific acknowledgement will be written to the ICS-04 standardized acknowledgement path under the `packet.destClientId` and `packet.sequence`. The sending chain can verify that the relayer-provided acknowledgment was committed to by the receiving chain by verifying this path using the on-chain client identified by `packet.sourceClientId`. Since the `packet.destClientId` is a unique reference to the sending chain on the destination chain and the `sequence` is unique in the stream of packets from source chain to destination; we can be guaranteed that the acknowledgement was written for the packet we previously sent with the provided `sourceClientId` and `sequence`. This acknowledgement is then given to the sending application to perform appropriate application logic for the given acknowledgement.
+
+The `TimeoutPacket` is called if the packet receipt is unsuccessful. All compliant implementations must write a sentinel non-empty value into the standardized ICS-04 receipt path if it successfully receives a packet. This receipt path is constructed using the `packet.destClientId` and `packet.sequence`. Thus, if the value does not exist after the packet timeout has been passed, we can be guaranteed that the packet has timed out. The sending chain verifies a relayer-provided `NonMembership` proof for the receipt path of the given packet, if it succeeds then the timeout is verified and the timeout logic for the sending application is executed. Note the nonmembership proof MUST be verified against a consensus state that is executed past the timeout timestamp of the packet, and packet receiving MUST fail on the destination after the timeout has elapsed. This ensures that a packet cannot be timed out on the source chain and received on the destination simultaneously.
+
+Thus, the packet handler implements the handlers for these messages by constructing the necessary path and value to authenticate the message as specified in [ICS-04](../core/ics-004-channel-and-packet-semantics/README); it then routes the verification of the membership/nonmembership proof to the relevant [ICS-02](../core/ics-002-client-semantics/README) client as specified in the packet. If the IBC TAO checks succeed and the client verification succeeds; then the packet message is authenticated and the application data in the payload can be processed by the application as trusted data. The packet sequence ensures that the stream of packets from a source chain to destination chain are all uniquely identified and prevents replay attacks. More detailed specification of the IBC TAO checks and packet handler behaviour can be found in [ICS-04](../core/ics-004-channel-and-packet-semantics/README).
+
+### Correctness
+
+Claim: If the clients are setup correctly, then a chain can always verify packet flow messages sent by a valid counterparty.
+
+If the clients are correct, then they can verify any key/value membership proof as well as a key non-membership proof.
+
+All packet flow message (SendPacket, RecvPacket, and TimeoutPacket) are sent with the full packet. The packet contains both sender and receiver identifiers. Thus on packet flow messages sent to the receiver (RecvPacket), we use the receiver identifier in the packet to retrieve our local client and the source identifier to determine which path the sender stored the packet under. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty.
+
+Similarly, for packet flow messages sent to the sender (AcknowledgePacket, TimeoutPacket); the packet is provided again. This time, we use the sender identifier to retrieve the local client and the destination identifier to determine the key path that the receiver must have written to when it received the packet. We can thus use our retrieved client to verify a key/value membership proof to validate that the packet was sent by the counterparty. In the case of timeout, if the packet receipt wasn't written to the receipt path determined by the destination identifier this can be verified by our retrieved client using the key nonmembership proof.
+
+### Soundness
+
+Claim: If the clients are setup correctly, then a chain cannot mistake a packet flow message intended for a different chain as a valid message from a valid counterparty.
+
+We must note that client identifiers are unique to each chain but are not globally unique. Let us first consider a user that correctly specifies the source and destination identifiers in the packet.
+
+We wish to ensure that well-formed packets (i.e. packets with correctly setup client ids) cannot have packet flow messages succeed on third-party chains. Ill-formed packets (i.e. packets with invalid client ids) may in some cases complete in invalid states; however we must ensure that any completed state from these packets cannot mix with the state of other valid packets.
+
+We are guaranteed that the source identifier is unique on the source chain, the destination identifier is unique on the destination chain. Additionally, the destination identifier points to a valid client of the source chain, and the source identifier points to a valid client of the destination chain.
+
+Suppose the RecvPacket is sent to a chain other than the one identified by the sourceClient on the source chain.
+
+In the packet flow messages sent to the receiver (RecvPacket), the packet send is verified using the client on the destination chain (retrieved using destination identifier) with the packet commitment path derived by the source identifier. This verification check can only pass if the chain identified by the destination client committed the packet we received under the source client identifier. This is only possible if the destination client is pointing to the original source chain, or if it is pointing to a different chain that committed the exact same packet. Pointing to the original source chain would mean we sent the packet to the correct . Since the sender only sends packets intended for the destination chain by setting to a unique source identifier, we can be sure the packet was indeed intended for us. Since our client on the receiver is also correctly pointing to the sender chain, we are verifying the proof against a specific consensus algorithm that we assume to be honest. If the packet is committed to the wrong key path, then we will not accept the packet. Similarly, if the packet is committed by the wrong chain then we will not be able to verify correctly.
diff --git a/ibc/next/spec/IBC_V2/core/ics-002-client-semantics/README.mdx b/ibc/next/spec/IBC_V2/core/ics-002-client-semantics/README.mdx
new file mode 100644
index 00000000..0f0f6b6a
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-002-client-semantics/README.mdx
@@ -0,0 +1,519 @@
+---
+ics: 2
+title: Client Semantics
+stage: draft
+category: IBC/TAO
+kind: interface
+requires: [24]
+required_by: [4]
+version_compatibility:
+ - ibc-go v10.0.0
+authors:
+ - name: Juwoon Yun
+ email: joon@tendermint.com
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2019-02-25
+modified: 2024-08-22
+---
+
+## Synopsis
+
+The IBC protocol provides secure packet flow between applications on different ledgers by verifying the packet messages using clients of the counterparty state machines. While ICS-4 defines the core packet flow logic between two chains and the provable commitments they must make in order to communicate, this standard ICS-2 specifies **how** a chain verifies the IBC provable commitments of the counterparty which is crucial to securely receive and process a packet flow message arriving from the counterparty.
+
+This standard focuses on how to keep track of the counterparty consensus and verify the state machine; it also specifies the properties that consensus algorithms of state machines implementing the inter-blockchain
+communication (IBC) protocol are required to satisfy.
+These properties are necessary for efficient and safe verification in the higher-level protocol abstractions.
+The algorithm utilised in IBC to verify the state updates of a remote state machine is referred to as a *validity predicate*.
+Pairing a validity predicate with a trusted state (i.e., a state that the verifier assumes to be correct),
+implements the functionality of a *light client* (often shortened to *client*) for a remote state machine on the host state machine.
+In addition to state update verification, every light client is able to detect consensus misbehaviours through a *misbehaviour predicate*.
+
+Beyond the properties described in this specification, IBC does not impose any requirements on
+the internal operation of the state machines and their consensus algorithms.
+A state machine may consist of a single process signing operations with a private key (the so-called "solo machine"), a quorum of processes signing in unison,
+many processes operating a Byzantine fault-tolerant consensus algorithm (e.g., Tendermint), or other configurations yet to be invented
+— from the perspective of IBC, a state machine is defined entirely by its light client validation and misbehaviour detection logic.
+
+This standard also specifies how the light client's functionality is registered and how its data is stored and updated by the IBC protocol.
+The stored client instances can be introspected by a third party actor,
+such as a user inspecting the state of the state machine and deciding whether or not to send an IBC packet.
+
+### Motivation
+
+The IBC protocol needs to be able to verify updates to the state of another state machine (i.e., the *remote state machine*).
+This entails accepting *only* the state updates that were agreed upon by the remote state machine's consensus algorithm.
+A light client of the remote state machine is the algorithm that enables the actor to verify state updates of that state machine.
+Note that light clients will generally not include validation of the entire state transition logic
+(as that would be equivalent to simply executing the other state machine), but may
+elect to validate parts of state transitions in particular cases.
+This standard formalises the light client model and requirements.
+As a result, the IBC protocol can easily be integrated with new state machines running new consensus algorithms,
+as long as the necessary light client algorithms fulfilling the listed requirements are provided.
+
+The IBC protocol can be used to interact with probabilistic-finality consensus algorithms.
+In such cases, different validity predicates may be required by different applications. For probabilistic-finality consensus, a validity predicate is defined by a finality threshold (e.g., the threshold defines how many block needs to be on top of a block in order to consider it finalized).
+As a result, clients could act as *thresholding views* of other clients:
+One *write-only* client could be used to store state updates (without the ability to verify them),
+while many *read-only* clients with different finality thresholds (confirmation depths after which
+state updates are considered final) are used to verify state updates.
+
+Client interfaces should also be constructed so that custom validation logic can be provided safely
+to define a custom client at runtime, as long as the underlying state machine can provide an
+appropriate gas metering mechanism to charge for compute and storage. On a host state machine
+which supports WASM execution, for example, the validity predicate and misbehaviour predicate
+could be provided as executable WASM functions when the client instance is created.
+
+### Definitions
+
+- `Consensus` is a state update generating algorithm. It takes the previous state of a state machine together
+ with a set of messages (i.e., state machine transactions) and generates a valid state update of the state machine.
+ Every state machine MUST have a `Consensus` that generates a unique, ordered list of state updates
+ starting from a genesis state.
+
+ This specification expects that the state updates generated by `Consensus`
+ satisfy the following properties:
+ - Every state update MUST NOT have more than one direct successor in the list of state updates.
+ In other words, the state machine MUST guarantee *finality* and *safety*.
+ - Every state update MUST eventually have a successor in the list of state updates.
+ In other words, the state machine MUST guarantee *liveness*.
+ - Every state update MUST be valid (i.e., valid state transitions).
+ In other words, `Consensus` MUST be *honest*,
+ e.g., in the case `Consensus` is a Byzantine fault-tolerant consensus algorithm,
+ such as Tendermint, less than a third of block producers MAY be Byzantine.
+
+ Unless the state machine satisfies all of the above properties, the IBC protocol
+may not work as intended, e.g., users' assets might be stolen. Note that specific client
+types may require additional properties.
+
+- `Height` specifies the order of the state updates of a state machine, e.g., a sequence number.
+ This entails that each state update is mapped to a `Height`.
+
+- `ClientMessage` is an arbitrary message defined by the client type that relayers can submit in order to update the client.
+ The ClientMessage may be intended as a regular update which may add new consensus state for proof verification, or it may contain
+ misbehaviour which should freeze the client.
+
+- `ValidityPredicate` is a function that validates a ClientMessage sent by a relayer in order to update the client.
+ Using the `ValidityPredicate` SHOULD be more computationally efficient than executing `Consensus`.
+
+```typescript
+type ValidityPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number) => (newConsensusState: bytes, newHeight: Number, err: Error)
+```
+
+- `ConsensusState` is the *trusted view* of the state of a state machine at a particular `Height`.
+ It MUST contain sufficient information to enable the `ValidityPredicate` to validate future state updates,
+ which can then be used to generate new `ConsensusState`s.
+
+- `ClientState` is the state of a client. It MUST expose an interface to higher-level protocol abstractions,
+ e.g., functions to verify proofs of the existence of particular values at particular paths at particular `Height`s.
+
+- `MisbehaviourPredicate` is a function that checks whether the rules of `Consensus` were broken,
+ in which case the client MUST be *frozen*, i.e., no subsequent `ConsensusState`s can be generated.
+ Verification against the client after it is frozen will also fail.
+
+```typescript
+type MisbehaviourPredicate = (clientState: bytes, trustedConsensusState: bytes, trustedHeight: Number, misbehaviour: bytes) => bool
+```
+
+- `Misbehaviour` is the proof needed by the `MisbehaviourPredicate` to determine whether
+ a violation of the consensus protocol occurred. For example, in the case the state machine
+ is a blockchain, a `Misbehaviour` might consist of two signed block headers with
+ different `ConsensusState` for the same `Height`.
+
+### Desired Properties
+
+Light clients MUST provide state verification functions that provide a secure way
+to verify the state of the remote state machines using the existing `ConsensusState`s.
+These state verification functions enable higher-level protocol abstractions to
+verify sub-components of the state of the remote state machines.
+
+`ValidityPredicate`s MUST reflect the behaviour of the remote state machine and its `Consensus`, i.e.,
+`ValidityPredicate`s accept *only* state updates that contain state updates generated by
+the `Consensus` of the remote state machine.
+
+In case of misbehavior, the behaviour of the `ValidityPredicate` might differ from the behaviour of
+the remote state machine and its `Consensus` (since clients do not execute the `Consensus` of the
+remote state machine). In this case, a `Misbehaviour` SHOULD be submitted to the host state machine,
+which would result in the client being frozen. Once the client is frozen, a recovery mechanism to address
+the situation must occur before client processing can presume. This recovery mechanism is out-of-scope
+of the IBC protocol as the specific recovery needed is highly case-dependent.
+
+## Technical Specification
+
+This specification outlines what each *client type* must define. A client type is a set of definitions
+of the data structures, initialisation logic, validity predicate, and misbehaviour predicate required
+to operate a light client. State machines implementing the IBC protocol can support any number of client
+types, and each client type can be instantiated with different initial consensus states in order to track
+different consensus instances.
+
+Specific client types and their specifications are defined in the light clients section of this repository.
+
+### Data Structures
+
+#### `Height`
+
+`Height` is an opaque data structure defined by a client type.
+It must form a partially ordered set & provide operations for comparison.
+
+```typescript
+type Height
+```
+
+```typescript
+enum Ord {
+ LT
+ EQ
+ GT
+}
+
+type compare = (h1: Height, h2: Height) => Ord
+```
+
+A height is either `LT` (less than), `EQ` (equal to), or `GT` (greater than) another height.
+
+`>=`, `>`, `===`, `<`, `<=` are defined through the rest of this specification as aliases to `compare`.
+
+There must also be a zero-element for a height type, referred to as `0`, which is less than all non-zero heights.
+
+#### `ConsensusState`
+
+`ConsensusState` is an opaque data structure defined by a client type, used by the validity predicate to
+verify new commits & state roots. Likely the structure will contain the last commit produced by
+the consensus process, including signatures and validator set metadata.
+
+`ConsensusState` MUST be generated from an instance of `Consensus`, which assigns unique heights
+for each `ConsensusState` (such that each height has exactly one associated consensus state).
+There MUST NOT be two valid `ConensusState`s for the same height.
+Such an event is called an "equivocation" and MUST be classified
+as misbehaviour. Should one occur, a proof should be generated and submitted so that the client can be frozen
+and previous state roots invalidated as necessary.
+
+```typescript
+type ConsensusState = bytes
+```
+
+The `ConsensusState` MUST define a `getTimestamp()` method which returns the timestamp **in seconds** associated with that consensus state.
+This timestamp MUST be the timestamp used in the counterparty state machine and agreed to by `Consensus`.
+
+```typescript
+type getTimestamp = ConsensusState => uint64
+```
+
+#### `ClientState`
+
+`ClientState` is an opaque data structure defined by a client type.
+It may keep arbitrary internal state to track verified roots and past misbehaviours.
+
+Light clients are representation-opaque — different consensus algorithms can define different light client update algorithms —
+but they must expose this common set of query functions to the IBC handler.
+
+```typescript
+type ClientState = bytes
+```
+
+Client types MUST define a method to initialise a client state with the provided client identifier, client state and consensus state, writing to internal state as appropriate.
+
+```typescript
+type initialise = (identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) => Void
+```
+
+Client types MUST define a method to fetch the current height (height of the most recent validated state update).
+
+```typescript
+type latestClientHeight = (
+ clientState: ClientState)
+ => Height
+```
+
+Client types MUST define a method on the client state to fetch the timestamp at a given height
+
+```typescript
+type getTimestampAtHeight = (
+ clientState: ClientState,
+ height: Height
+) => uint64
+```
+
+#### `ClientMessage`
+
+A `ClientMessage` is an opaque data structure defined by a client type which provides information to update the client.
+`ClientMessage`s can be submitted to an associated client to add new `ConsensusState`(s) and/or update the `ClientState`. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate.
+
+```typescript
+type ClientMessage = bytes
+```
+
+#### `CommitmentProof`
+
+`CommitmentProof` is an opaque data structure defined by the client type.
+
+```typescript
+type CommitmentProof = bytes
+```
+
+It is utilised to verify presence or absence of a particular key/value pair in state
+at a particular finalised height (necessarily associated with a particular commitment root).
+
+### State verification
+
+Client types must define functions to authenticate internal state of the state machine which the client tracks.
+Internal implementation details may differ (for example, a loopback client could simply read directly from the state and require no proofs).
+
+`verifyMembership` is a generic proof verification method which verifies a proof of the existence of a value at a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful.
+The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 4](../ics-004-packet-semantics/PACKET)).
+
+```typescript
+type verifyMembership = (
+ clientState: ClientState,
+ height: Height,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: bytes)
+ => Error
+```
+
+`verifyNonMembership` is a generic proof verification method which verifies a proof of absence of a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful.
+The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 24](../ics-024-host-requirements/README#path-space)).
+
+Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. Thus in these special cases, the proof provided will be an Existence proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height.
+
+```typescript
+type verifyNonMembership = (
+ clientState: ClientState,
+ height: Height,
+ proof: CommitmentProof,
+ path: CommitmentPath)
+ => Error
+```
+
+#### Implementation strategies
+
+##### Loopback
+
+A loopback client of a local state machine merely reads from the local state, to which it must have access.
+
+##### Simple signatures
+
+A client of a solo state machine with a known public key checks signatures on messages sent by that local state machine,
+which are provided as the `Proof` parameter. The `height` parameter can be used as a replay protection nonce.
+
+Multi-signature or threshold signature schemes can also be used in such a fashion.
+
+##### Proxy clients
+
+Proxy clients verify another (proxy) state machine's verification of the target state machine, by including in the
+proof first a proof of the client state on the proxy state machine, and then a secondary proof of the sub-state of
+the target state machine with respect to the client state on the proxy state machine. This allows the proxy client to
+avoid storing and tracking the consensus state of the target state machine itself, at the cost of adding
+security assumptions of proxy state machine correctness.
+
+##### Merklized state trees
+
+For clients of state machines with Merklized state trees, these functions can be implemented as MerkleTree Existence and NonExistence proofs. Client implementations may choose to implement these methods for the specific tree used by the counterparty chain or they can use the tree-generic [ICS-23](https://github.com/cosmos/ics23) `verifyMembership` or `verifyNonMembership` methods, using a verified Merkle
+root stored in the `ClientState`, to verify presence or absence of particular key/value pairs in state at particular heights for any ICS-23 compliant tree given a ProofSpec that describes how the tree is constructed. In this case, the ICS-23 `ProofSpec` MUST be provided to the client on initialization.
+
+### Sub-protocols
+
+IBC handlers MUST implement the functions defined below.
+
+#### Identifier validation
+
+Clients are stored under a unique `Identifier` prefix.
+This ICS does not require that client identifiers be generated in a particular manner, only that they be unique.
+However, it is possible to restrict the space of `Identifier`s if required.
+The validation function `validateClientIdentifier` MAY be provided.
+
+```typescript
+type validateClientIdentifier = (id: Identifier) => boolean
+```
+
+If not provided, the default `validateClientIdentifier` will always return `true`.
+
+##### Utilising past roots
+
+To avoid race conditions between client updates (which change the state root) and proof-carrying
+transactions in handshakes or packet receipt, many IBC handler functions allow the caller to specify
+a particular past root to reference, which is looked up by height. IBC handler functions which do this
+must ensure that they also perform any requisite checks on the height passed in by the caller to ensure
+logical correctness.
+
+#### CreateClient
+
+Calling `createClient` with the client state and initial consensus state creates a new client. The intiator of this client is responsible for setting all of the initial parameters of the `ClientState` and the initial root-of-trust `ConsensusState`. The client implementation is then responsible for executing the light client `ValidityPredicate` against these initial parameters. Thus, once a root-of-trust is instantiated; the light client guarantees to preserve that trust within the confines of the security model as parameterized by the `ClientState`. If a user verifies that a client is a valid client of the counterparty chain once, they can be guaranteed that it will remain a valid client into the future so long as the `MisbehaviourPredicate` is not triggered. If the `MisbehaviourPredicate` is triggered however, this can be submitted as misbehaviour to freeze the IBC light client operations.
+
+CreateClient Inputs:
+
+`clientType: string`: This is the client-type that references a particular light client implementation on the chain. The `CreateClient` message will create a new instance of the given client-type.
+`ClientState: bytes`: This is the opaque client state as defined for the given client type. It will contain any parameters needed for verifying client updates and proof verification against a `ConsensusState`. The `ClientState` parameterizes the security model as implemented by the client type.
+`ConsensusState: bytes`: This is the opaque consensus state as defined for the given client type. It is the initial consensus state provided and MUST be capable of being used by the `ValidityPredicate` to add new `ConsensusState`s to the client. The initial `ConsensusState` MAY also be used for proof verification but it is not necessary.
+`Height: Number`: This is the height that is associated with the initial consensus state.
+
+CreateClient Preconditions:
+
+- The provided `clientType` is supported by the chain and can be routed to by the IBC handler.
+
+CreateClient PostConditions:
+
+- A unique identifier `clientId` is generated for the client
+- The provided `ClientState` is persisted to state and retrievable given the `clientId`.
+- The provided `ConsensusState` is persisted to state and retrievable given the `clientId` and `height`.
+
+CreateClient ErrorConditions:
+
+- The provided `ClientState` is invalid given the client type.
+- The provided `ConsensusState` is invalid given the client type.
+- The `Height` is not a positive number.
+
+#### RegisterCounterparty
+
+IBC Version 2 introduces a `registerCounterparty` procedure. Calling `registerCounterparty` with the clientId will register the counterparty clientId
+that the counterparty will use to write packet messages intended for our chain. All ICS24 provable paths to our chain will be keyed on the counterparty clientId, so each client must be aware of the counterparty's identifier in order to construct the path for key verification and ensure there is an authenticated stream of packet data between the clients that do not get written to by other clients.
+The `registerCounterparty` also includes the `CommitmentPrefix` to use for the counterparty chain. Most chains will not store the ICS24 directly under the root of a MerkleTree and will instead store the standardized paths under a custom prefix, thus the counterparty client must be given this information to verify proofs correctly. The `CommitmentPrefix` is defined as an array of byte arrays to support nested Merkle trees. In this case, each element of the outer array is a key for each tree in the nested structure ordered from the top-most tree to the lowest level tree. In this case, the ICS24 path is appended to the key of the lowest-level tree (i.e. the last element of the commitment prefix) in order to get the full `CommitmentPath` for proof verification.
+
+RegisterCounterparty Inputs:
+
+`clientId: bytes`: The clientId on the executing chain.
+`counterpartyClientId: bytes`: The identifier of the client used by the counterparty chain to verify the executing chain.
+`counterpartyCommitmentPrefix: []bytes`: The prefix used by the counterparty chain.
+
+RegisterCounterparty Preconditions:
+
+- A client has already been created for the `clientId`
+
+RegisterCounterparty Postconditions:
+
+- The `counterpartyClientId` is retrievable given the `clientId`.
+- The `counterpartyCommitmentPrefix` is retrievable given the `clientId`.
+
+RegisterCounterparty ErrorConditions:
+
+- There does not exist a client for the given `clientId`
+- `RegisterCounterparty` has already been called for the given `clientId`
+
+NOTE: Once the clients and counterparties have been registered on both sides, the connection between the clients is established and packet flow between the clients may commence. Users are expected to verify that the clients and counterparties are set correctly before using the connection to send packets. They may do this directly themselves or through social consensus.
+NOTE: `RegisterCounterparty` is setting information that will be crucial for proper proof verification of IBC messages using our client. Thus, it must be authenticated properly. The `RegisterCounterparty` message can be permissionless in which case the fields must be authenticated against the counterparty chain using the client which may prove difficult and cumbersome. It is RECOMMENDED to simply ensure that the client creator address is the same as the one that registers the counterparty. Once the client and counterparty are set by the same creator, users can decide if the configuration is secure out-of-band.
+
+#### Update
+
+Updating a client is done by submitting a new `ClientMessage`. The `Identifier` is used to point to the
+stored `ClientState` that the logic will update. When a new `ClientMessage` is verified using the `ValidityPredicate` with
+the stored `ClientState` and a previously stored `ConsensusState`, the client MUST then add a new `ConsensusState` with a new `Height`.
+
+If a client can no longer be updated (if, for example, the trusting period has passed),
+then new packet flow will not be able to be processed. Manual intervention must take place to
+reset the client state or migrate the client. This
+cannot safely be done completely automatically, but chains implementing IBC could elect
+to allow governance mechanisms to perform these actions
+(perhaps even per-client/connection/channel in a multi-sig or contract).
+
+UpdateClient Inputs:
+
+`clientId: bytes`: The identifier of the client being updated.
+`clientMessage: bytes`: The opaque clientMessage to update the client as defined by the given `clientType`. It MUST include the `trustedHeight` we wish to update from. This `trustedHeight` will be used to retrieve a trusted ConsensusState which we will use to update to a new consensus state using the `ValidityPredicate`.
+
+UpdateClient Preconditions:
+
+- A client has already been created for the `clientId`
+
+UpdateClient Postconditions:
+
+- A new `ConsensusState` is added to the client and persisted with a new `Height`
+- Implementations MAY automatically detect misbehaviour in `UpdateClient` if the update itself is proof of misbehaviour (e.g. There is already a different `ConsensusState` for the given height, or time monotonicity is broken). It is recommended to automatically freeze the client in this case to avoid having to send a redundant `submitMisbehaviour` message.
+
+UpdateClient ErrorConditions:
+
+- The trusted `ConsensusState` referenced in the `ClientMessage` does not exist in state
+- `ValidityPredicate(clientState, trustedConsensusState, trustedHeight)` returns an error
+
+#### Misbehaviour
+
+If `Consensus` of the counterparty chain is violated, then the relayer can submit proof of this as misbehaviour. Once the client is frozen, no updates may take place and all proof verification will fail. The client may be unfrozen by an out-of-band protocol once trust in the counterparty `Consensus` is restored and any invalid state caused by the break in `Consensus` is reverted on the executing chain.
+
+SubmitMisbehaviour Inputs:
+`clientId: bytes`: The identifier of the client being frozen.
+`clientMessage: bytes`: The opaque clientMessage to freeze the client as defined by the given `clientType`. It MUST include the `trustedHeight` we wish to verify misbehaviour from. This `trustedHeight` will be used to retrieve a trusted ConsensusState which we will use to freeze the client given the `MisbehaviourPredicate`. It MUST also include the misbehaviour being submitted.
+
+SubmitMisbehaviour Preconditions:
+
+- A client has already been created for the `clientId`.
+
+SubmitMisbehaviour Postconditions:
+
+- The client is frozen, update and proof verification will fail until client is unfrozen again.
+
+SubmitMisbehaviour ErrorConditions:
+
+- The trusted `ConsensusState` referenced in the `ClientMessage` does not exist in state.
+- `MisbehaviourPredicate(clientState, trustedConsensusState, trustedHeight, misbehaviour)` returns `false`.
+
+### VerifyMembership and VerifyNonmembership
+
+The IBC core packet handler uses the consensus states created in `UpdateClient` to verify ICS-4 standardized paths to authenticate packet messages. In order to do this, the IBC packet handler constructs the expected key/value for the given packet flow message and sends the expected path and value to the client along with the relayer-provided proof to the client for verification. Note that the proof is relayer provided, but the path and value are constructed by the IBC packet handler for the given packet. Thus, the relayer cannot forge proofs for packets that did not get sent. IBC Packet handler must also have the ability to prove nonmembership of a given path in order to enable timeout processing. Thus, clients must expose the following `verifyMembership` and `verifyNonMembership` methods:
+
+```typescript
+type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean
+```
+
+```typescript
+type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean
+```
+
+ProofVerification Inputs:
+
+- `clientId: bytes`: The identifier of the client that will verify the proof.
+- `Height: Number`: The height for the consensus state that the proof will be verified against.
+- `Path: CommitmentPath`: The path of the key being proven. In the IBC protocol, this will be an ICS24 standardized path prefixed by the `CommitmentPrefix` registered on the counterparty. The `Path` MUST be constructed by the IBC handler given the IBC message, it MUST NOT be provided by the relayer as the relayer is untrusted.
+- `Value: Optional`: The value being proven. If it is non-empty this is a membership proof. If the value is nil, this is a non-membership proof.
+
+ProofVerification Preconditions:
+
+- A client has already been created for the `clientId`.
+- A `ConsensusState` is stored for the given `Height`.
+
+ProofVerification Postconditions:
+
+- Proof verification should be stateless in most cases. In the case that the proof verification is a signature check, we may wish to increment a nonce to prevent replay attacks.
+
+ProofVerification Errorconditions:
+
+- `CommitmentProof` does not successfully verify with the provided `CommitmentPath` and `Value` with the retrieved `ConsensusState` for the provided `Height`.
+
+### Properties & Invariants
+
+- Client identifiers are immutable & first-come-first-serve. Clients cannot be deleted (allowing deletion would potentially allow future replay of past packets if identifiers were re-used).
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+New client types can be added by IBC implementations at-will as long as they conform to this interface.
+
+## Example Implementations
+
+Please see the ibc-go implementations of light clients for examples of how to implement your own: (https://github.com/cosmos/ibc-go/blob/main/modules/light-clients).
+
+## History
+
+Mar 5, 2019 - Initial draft finished and submitted as a PR
+
+May 29, 2019 - Various revisions, notably multiple commitment-roots
+
+Aug 15, 2019 - Major rework for clarity around client interface
+
+Jan 13, 2020 - Revisions for client type separation & path alterations
+
+Jan 26, 2020 - Addition of query interface
+
+Jul 27, 2022 - Addition of `verifyClientState` function, and move `ClientState` to the `provableStore`
+
+August 4, 2022 - Changes to ClientState interface and associated handler to align with changes in 02-client-refactor ADR: (https://github.com/cosmos/ibc-go/pull/1871)
+
+August 22, 2024 - [Changes for IBC/TAO V2](https://github.com/cosmos/ibc/pull/1147)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.mdx b/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.mdx
new file mode 100644
index 00000000..bd9c18be
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET.mdx
@@ -0,0 +1,172 @@
+---
+title: "Packet Structure and Provable Commitment Specification"
+---
+
+## Packet V2 Structure
+
+The IBC packet sends application data from a source chain to a destination chain with a timeout that specifies when the packet is no longer valid. The packet will be committed to by the source chain as specified in the ICS-24 specification. The receiver chain will then verify the packet commitment under the ICS-24 specified packet commitment path. If the proof succeeds, the IBC handler sends the application data(s) to the relevant application(s).
+
+```typescript
+interface Packet {
+ // identifier for the destination-chain client existing on source chain
+ sourceClientId: bytes,
+ // identifier for the source-chain client existing on destination chain
+ destClientId: bytes,
+ // the sequence uniquely identifies this packet
+ // in the stream of packets from source to dest chain
+ sequence: uint64,
+ // the timeout is the timestamp in seconds on the destination chain
+ // at which point the packet is no longer valid.
+ // It cannot be received on the destination chain and can
+ // be timed out on the source chain
+ timeout: uint64,
+ // the data includes the messages that are intended
+ // to be sent to application(s) on the destination chain
+ // from application(s) on the source chain
+ // IBC core handlers will route the payload to the desired
+ // application using the port identifiers but the rest of the
+ // payload will be processed by the application
+ data: [Payload]
+}
+
+interface Payload {
+ // sourcePort identifies the sending application on the source chain
+ sourcePort: bytes,
+ // destPort identifies the receiving application on the dest chain
+ destPort: bytes,
+ // version identifies the version that sending application
+ // expects destination chain to use in processing the message
+ // if dest chain does not support the version, the payload must
+ // be rejected with an error acknowledgement
+ version: string,
+ // encoding allows the sending application to specify which
+ // encoding was used to encode the app data
+ // the receiving applicaton will decode the appData into
+ // the strucure expected given the version provided
+ // if the encoding is not supported, receiving application
+ // must be rejected with an error acknowledgement.
+ // the encoding string MUST be in MIME format
+ encoding: string,
+ // appData is the opaque content sent from the source application
+ // to the dest application. It will be decoded and interpreted
+ // as specified by the version and encoding fields
+ appData: bytes,
+}
+```
+
+The source and destination client identifiers at the top-level of the packet identify the chains communicating. The `sourceClientId` identifier **must** be unique on the source chain and is a pointer to the destination chain client on the source chain. The `destClientId` identifier **must** be a unique identifier on the destination chain and is a pointer to the source chain client on the destination chain. The sequence is a monotonically incrementing nonce to uniquely identify packets sent between the source and destination chain.
+
+The timeout is the UNIX timestamp in seconds that must be passed on the **destination** chain before the packet is invalid and no longer capable of being received. Note that the timeout timestamp is assessed against the destination chain's clock which may drift relative to the clocks of the sender chain or a third party observer. If a packet is received on the destination chain after the timeout timestamp has passed relative to the destination chain's clock; the packet must be rejected so that it can be safely timed out and reverted by the sender chain.
+
+In version 2 of the IBC specification, implementations **MAY** support multiple application data within the same packet. This can be represented by a list of payloads. Implementations may choose to only support a single payload per packet, in which case they can just reject incoming packets sent with multiple payloads.
+
+Each payload will include its own `Encoding` and `AppVersion` that will be sent to the application to instruct it how to decode and interpret the opaque application data. The application must be able to support the provided `Encoding` and `AppVersion` in order to process the `AppData`. If the receiving application does not support the encoding or app version, then the application **must** return an error to IBC core. If the receiving application does support the provided encoding and app version, then the application must decode the application as specified by the `Encoding` string and then process the application as expected by the counterparty given the agreed-upon app version. Since the `Encoding` and `AppVersion` are now in each packet they can be changed on a per-packet basis and an application can simultaneously support many encodings and app versions from a counterparty. This is in stark contrast to IBC version 1 where the channel prenegotiated the channel version (which implicitly negotiates the encoding as well); so that changing the app version after channel opening is very difficult.
+
+All implementations must commit the packet in the standardized IBC commitment format to satisfy the protocol. In order to do this we must first commit the packet data and timeout. The timeout is encoded in LittleEndian format. The packet data which is a list of payloads is committed to by hashing each individual field of the payload and successively concatenating them together. This ensures a standard unambigious commitment for a given packet. Thus a given packet will always create the exact same commitment by all compliant implementations and two different packets will never create the same commitment by a compliant implementation. This commitment value is then stored under the standardized provable packet commitment key as defined below:
+
+```typescript
+func packetCommitmentPath(packet: Packet): bytes {
+ return packet.sourceClientId + byte(0x01) + bigEndian(packet.sequence)
+}
+```
+
+```typescript
+// commitPayload hashes all the fields of the packet data to create a standard size
+// preimage before committing it in the packet.
+func commitPayload(payload: Payload): bytes {
+ buffer = sha256.Hash(payload.sourcePort)
+ buffer = append(sha256.Hash(payload.destPort))
+ buffer = append(sha256.Hash(payload.version))
+ buffer = append(sha256.Hash(payload.encoding))
+ buffer = append(sha256.Hash(payload.appData))
+ return sha256.Hash(buffer)
+}
+
+// commitV2Packet commits to all fields in the packet
+// by hashing each individual field and then hashing these fields together
+// Note: SourceClient and the sequence are omitted since they will be included in the key
+// Every other field of the packet is committed to in the packet which will be stored in the
+// packet commitment value
+// The final preimage will be prepended by the byte 0x02 before hashing in order to clearly define the protocol version
+// and allow for future upgradability
+func commitV2Packet(packet: Packet) {
+ timeoutBytes = LittleEndian(packet.timeout)
+ var appBytes: bytes
+ for p in packet.payload {
+ appBytes = append(appBytes, commitPayload(p))
+ }
+ buffer = sha256.Hash(packet.destClient)
+ buffer = append(buffer, sha256.hash(timeoutBytes))
+ buffer = append(buffer, sha256.hash(appBytes))
+ buffer = append([]byte{0x02}, buffer)
+ return sha256.Hash(buffer)
+}
+```
+
+## Acknowledgement V2
+
+The acknowledgement in the version 2 specification is also modified to support multiple payloads in the packet that will each go to separate applications that can write their own acknowledgements. Each acknowledgment will be contained within the final packet acknowledgment in the same order that they were received in the original packet. Thus if a packet contains payloads for modules `A` and `B` in that order; the receiver will write an acknowledgment with the app acknowledgements `A` and `B` in the same order.
+
+The acknowledgement which is itself a list of app acknowledgement bytes must be committed to by hashing each individual acknowledgement and concatenating them together and hashing the result. This ensures that all compliant implementations reach the same acknowledgment commitment and that two different acknowledgements never create the same commitment.
+
+An application may not need to return an acknowledgment. In this case, it may return a sentinel acknowledgement value `SENTINEL_ACKNOWLEDGMENT` which will be the single byte in the byte array: `bytes(0x01)`. In this case, the IBC `acknowledgePacket` handler will still do the core IBC acknowledgment logic but it will not call the application's acknowledgePacket callback.
+
+```typescript
+interface Acknowledgement {
+ // Each app in the payload will have an acknowledgment in this list in the same order
+ // that they were received in the payload
+ // If an app does not need to send an acknowledgement, there must be a SENTINEL_ACKNOWLEDGEMENT
+ // in its place
+ // The app acknowledgement must be encoded in the same manner specified in the payload it received
+ // and must be created and processed in the manner expected by the version specified in the payload.
+ appAcknowledgement: [bytes]
+}
+```
+
+All acknowledgements must be committed to and stored under the standardized acknowledgment path. Note that since each acknowledgement is associated with a given received packet, the acnowledgement path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the acknowledgement.
+
+```typescript
+func acknowledgementPath(packet: Packet) {
+ return packet.destClientId + byte(0x02) + bigEndian(packet.Sequence)
+}
+```
+
+```typescript
+// commitV2Acknowledgement hashes each app acknowledgment and hashes them together
+// the final preimage will be prepended with the byte 0x02 before hashing in order to clearly define the protocol version
+// and allow for future upgradability
+func commitV2Acknowledgment(ack: Acknowledgement) {
+ var buffer: bytes
+ for appAck in ack.appAcknowledgement {
+ buffer = append(buffer, sha256.Hash(appAck))
+ }
+ buffer = append([]byte{0x02}, buffer)
+ return sha256.Hash(buffer)
+}
+```
+
+## Packet Receipt V2
+
+A packet receipt will only tell the sending chain that the counterparty has successfully received the packet. Thus we just need a provable boolean flag uniquely associated with the sent packet. Thus, the receiver chain stores the packet receipt keyed on the destination identifier and the sequence to uniquely identify the packet.
+
+For chains that support nonexistence proofs of their own state, they can simply write a `SENTINEL_RECEIPT_VALUE` under the receipt path. This `SENTINEL_RECEIPT_PATH` can be any non-nil value so it is recommended to write a single byte. The receipt path is standardized as below. Similar to the acknowledgement, each receipt is associated with a given received packet the receipt path is constructed using the packet `destClientId` and its `sequence` to generate a unique key for the receipt.
+
+```typescript
+func receiptPath(packet: Packet) {
+ return packet.destClientId + byte(0x03) + bigEndian(packet.Sequence)
+}
+```
+
+## Provable Path-space
+
+IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path. The `provableStore` is specified in [ICS24 Host Requirements](../ics-024-host-requirements/README)
+
+Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler.
+
+| Value | Path format |
+| -------------------------- | ---------------------------------------------- |
+| Packet Commitment | {sourceClientId}0x1{bigEndianUint64Sequence} |
+| Packet Receipt | {destClientId}0x2{bigEndianUint64Sequence} |
+| Acknowledgement Commitment | {destClientId}0x3{bigEndianUint64Sequence} |
+
+Note that the IBC protocol ensures that the packet `(sourceClientId, sequence)` tuple uniquely identifies a packet on the sending chain, and the `(destClientId, sequence)` tuple uniquely identifies a packet on the receiving chain. This property along with the byte separator between the client identifier and sequence in the standardized paths ensures that commitments, receipts, and acknowledgements are each written to different paths for the same packet. Thus, so long as the host requirements specified in ICS24 are respected; a provable key written to state by the IBC handler for a given packet will never be overwritten with a different value. This ensures secure and correct communication between chains in the IBC ecosystem.
diff --git a/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.mdx b/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.mdx
new file mode 100644
index 00000000..6df069dd
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-004-packet-semantics/PACKET_HANDLER.mdx
@@ -0,0 +1,208 @@
+---
+title: "IBC Packet Handler"
+---
+
+The packet handler specification defines the semantics and behavior that implementations must enforce in order to support IBC v2 protocol.
+
+## Packet Structure
+
+A `Packet` in the interblockchain communication protocol is the primary interface by which applications will send data to counterparty applications on other chains. It is defined as follows:
+
+```typescript
+interface Packet {
+ sourceClientId: bytes // identifier of the client on the sending chain
+ destClientId: bytes // identifier of the client on the receiving chain
+ sequence: uint64 // unique number identifying this packet in the stream of packets from sourceClientId to destClientId
+ timeoutTimestamp: uint64, // indicates the timeout as a UNIX timestamp in seconds. If the timeout timestamp is reached on destination chain, it is no longer receivable
+ data: Payload[] // a list of payloads intended for applications on the receiving chain
+}
+```
+
+```typescript
+interface Payload {
+ sourcePort: bytes, // identifier of the sending application on the sending chain
+ destPort: bytes, // identifier of the receiving application on the receiving chain
+ version: string, // payload version only interpretable by sending/receiving applications
+ encoding: string, // payload encoding only interpretable by sending/receiving applications
+ value: bytes // application-specific data that can be parsed by receiving application given the version and encoding
+}
+```
+
+The packet is never directly serialised and sent to counterparty chains. Instead a standardized non-malleable committment to the packet data is stored under the standardized unique key for the packet as defined in ICS-24. Thus, implementations MAY make individual choices on the exact packet structure and serialization scheme they use internally so long as they respect the standardized commitment defined by the IBC protocol when writing to the provable store.
+
+Packet Invariants:
+
+- None of the packet fields are allowed to be empty
+- For every payload included, none of the payload fields are allowed to be empty
+
+## Receipt
+
+A `Receipt` is a sentinel byte that is stored under the standardized provable ReceiptPath of a given packet by the receiving chain when it successfully receives the packet. This prevents replay attacks and also the possibility of timing out a packet on the sender chain when the packet has already been received. The specific value of the receipt does not matter so long as its not empty.
+
+## Acknowledgement Structure
+
+An `Acknowledgement` is the interface that will be used by receiving applications to return application specific information back to the sender. If every application successfully received its payload, then each receiving application will return their custom acknowledgement bytes which will be appended to the acknowledgement array. If **any** application returns an error, then the acknowledgement will have a single element with a sentinel error acknowledgement.
+
+```typescript
+const ErrorAcknowledgement = sha256("UNIVERSAL_ERROR_ACKNOWLEDGEMENT")
+
+interface Acknowledgement {
+ appAcknowledgement bytes[] // array of an array of bytes. Each element of the array contains an acknowledgement from a specific application
+}
+```
+
+Acknowledgement Invariants:
+
+- If the acknowledgement interface includes an error acknowledgement then there must be only a single element in the array with the error acknowledgement
+- There CANNOT be multiple app acknowledgements where an element is the error acknowledgement
+- If there are multiple app acknowledgements, the length of the app acknowledgements is the same length as the payloads in the associated packet and each acknowledgement is associated with the payload in the same position in the payload array.
+
+## SendPacket
+
+SendPacket is called by users to execute an inter-blockchain flow. The user submits a message with a payload(s) for each IBC application they wish to interact with. The SendPacket handler must call the sendPacket logic of each sending application as identified by the sourcePort of the payload. If none of the sending applications error, then the sendPacket handler must construct the packet with the user-provided sourceClient, payloads, and timeout and the destinationClient it retrieves from the counterparty storage given the sourceClient and a generated sequence that is unique for the sourceClientId. It will commit the packet with the ICS24 commitment function under the ICS24 path. The sending chain MAY store the ICS24 path under a custom prefix in the provable store. In this case, the counterparty must have knowledge of the custom prefix as provided by the relayer on setup. The sending chain SHOULD check the provided timestamp against an authenticated time oracle (local BFT time or destination client latest timestamp) and preemptively reject a user-provided packet with a timestamp that has already passed.
+
+The user may be an off-chain process or an on-chain actor. In either case, the user is not trusted by the IBC protocol. The IBC application is responsible for properly authenticating that the user is allowed to send the requested app data using the IBC application's port as specified in the source port of the payload. The IBC application is also responsible for executing any app-specific logic that must run before the IBC packet can be sent (e.g. escrowing user's tokens before sending a fungible token transfer packet).
+
+SendPacket Inputs:
+
+`payloads: Payload[]`: List of payloads that are to be sent from source applications on sending chain to corresponding destination applications on the receiving chain. Implementations MAY choose to only support a single payload per packet.
+`sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain.
+`timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks
+
+SendPacket Preconditions:
+
+- A valid client exists on the sending chain with the `sourceClientId`
+- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty`
+
+SendPacket Postconditions:
+
+- The sending application(s) as identified by the source port(s) in the payload(s) have all executed their sendPacket logic successfully
+- The following packet gets committed and stored under the packet commitment path as specified by ICS24:
+
+```typescript
+interface Packet {
+ sourceClientId: sourceClientId,
+ destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId
+ sequence: generateUniqueSequence(sourceClientId),
+ timeoutTimestamp: timeoutTimestamp
+ data: payloads
+}
+```
+
+- Since the packet is committed to with a hash in-state, implementations must provide the packet fields for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system.
+
+SendPacket Errorconditions:
+
+- Any of the sending applications returns an error during its sendPacket logic execution
+- The sending client is invalid (expired or frozen)
+
+SendPacket Invariants:
+
+- The sourceClientId MUST exist on the sending chain
+- The destClientId MUST be the registered counterparty of the sourceClientId on the sending chain
+- The sending chain MUST NOT have sent a previous packet with the same `sourceClientId` and `sequence`
+
+## RecvPacket
+
+RecvPacket is called by relayers once a packet has been committed on the sender chain in order to process the packet on the receiving chain. Since the relayer is not trusted, the relayer must provide a proof that the sender chain had indeed committed the provided packet which will be verified against the `destClient` on the receiving chain.
+
+If the proof succeeds, and the packet passes replay and timeout checks; then each payload is sent to the receiving application as part of the receiving application callback.
+
+RecvPacket Inputs:
+`packet: Packet`: The packet sent from the sending chain to our chain
+`proof: bytes`: An opaque proof that will be sent to the destination client. The destination client is responsible for interpreting the bytes as a proof and verifying the packet commitment key/value provided by the packet handler against the provided proof.
+`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the destination client for the proof to verify correctly.
+
+RecvPacket Preconditions:
+
+- A valid client exists on the receiving chain with `destClientId`
+- There exists a mapping from `destClientId` to `Counterparty`
+
+RecvPacket Postconditions:
+
+- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence`
+- All receiving application(s) as identified by the destPort(s) in the payload(s) have executed their recvPacket logic. If **any** of the payloads return an error during processing, then all application state changes for all payloads **must** be reverted. If all payloads are processed successfully, then all applications state changes are written. This ensures atomic execution for the payloads batched together in a single packet.
+- If any payload returns an error, then the single `SENTINEL ERROR ACKNOWLEDGEMENT` is written using `WriteAcknowledgment`. If all payloads succeed and return an app-specific acknowledgement, then each app acknowledgement is included in the list of `AppAcknowledgement` in the final packet `Acknowledgement` in the **exact** order that their corresponding payloads were included in the packet.
+
+NOTE: It is possible for applications to process their payload asynchronously to the `RecvPacket` transaction execution. In this case, the IBC core handler **must** await all applications returning their individual application acknowledgements before writing the acknowledgement with app acknowledgements in the order of their corresponding payloads in the original packet **not** the order in which the applications return their asynchronous acknowledgements which may be different orders. IBC allows multiple payloads intended for the same application to be batched in the same packet. Thus, if an implementation wishes to support multiple payloads and asynchronous acknowledgements together, then there must be a way for core IBC to know which payload a particular acknowledgment is being written for. This may be done by providing the index of the payload list during `recvPacket` application callback, so that the application can return the same index when writing the acknowledgment so that it can be placed in the right order. Otherwise, implementations may simply block asynchronous acknowledgment support for multi-payload packets
+
+RecvPacket Errorconditions:
+
+- `Counterparty.ClientId` != `packet.sourceClientId` ensures that packet was sent by expected counterparty
+- `packet.TimeoutTimestamp` >= `chain.BlockTime()` ensures we cannot receive successfully if packet can be timed out on sending chain
+- Packet receipt does not already exist in state for the `destClientId` and `sequence`. This prevents replay attacks
+- Membership proof does not successfully verify
+
+## WriteAcknowledgement
+
+WriteAcknowledgement Inputs:
+
+`destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain
+`sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain
+`ack: Acknowledgement`: Acknowledgement collected by receiving chain from all receiving applications after they have returned their individual acknowledgement. If any individual application errors, the entire acknowledgement MUST have a single element with just the SENTINEL ERROR ACKNOWLEDGEMENT. If all applications successfully received, then every application must have its own acknowledgement set in the `Acknowledgement` in the same order that they existed in the payload of the sending packet.
+
+WriteAcknowledgement Preconditions:
+
+- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence`
+- An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path
+
+WriteAcknowledgement Postconditions:
+
+- The acknowledgement is committed and written to the acknowledgement path as specified in ICS24
+- Since the acknowledgement is being hashed, the full acknowledgement fields should be made available for relayers to reconstruct. This can be emitted in an event system or stored in state as the full packet under an auxilliary key if the implementing platform does not have an event system.
+- Implementors SHOULD also emit the full packet again in `WriteAcknowledgement` since the sender chain is only expected to store the packet commitment and not the full packet; relayers are expected to pass the packet back to the sender chain to process the acknowledgement. Thus, in order to support stateless relayers it is helpful to re-emit the packet fields on `WriteAcknowledgement` so the relayer can reconstruct the packet.
+- If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state
+- If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet.
+
+## AcknowledgePacket
+
+AcknowledgePacket Inputs:
+
+`packet: Packet`: The packet that was originally sent by our chain
+`acknowledgement: Acknowledgement`: The acknowledgement written by the receiving chain for the packet
+`proof: bytes`: An opaque proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the acknowledgement key/value provided by the packet handler.
+`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly.
+
+AcknowledgePacket Preconditions:
+
+- A valid client exists on the sending chain with the `sourceClientId`
+- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty`
+- A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence`
+
+AcknowledgePacket Postconditions:
+
+- All sending applications execute the ackPacket logic with the payload and the individual acknowledgement for that payload or the universal `ErrorAcknowledgement`.
+- Stored commitment for the packet is deleted
+
+AcknowledgePacket Errorconditions:
+
+- `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier
+- The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence`
+- Membership proof of the acknowledgement commitment on the receiving chain as standardized by ICS24 does not verify
+- Any of the applications return an error during the `AcknowledgePacket` callback for their payload. Applications should generally not error on AcknowledgePacket. If this occurs, it is most likely a bug so the error should revert the transaction and allow for the bug to be patched before resubmitting the transaction.
+
+## TimeoutPacket
+
+TimeoutPacket Inputs:
+
+`packet: Packet`: The packet that was originally sent by our chain
+`proof: bytes`: An opaque non-existence proof that will be sent to the source client. The source client is responsible for interpreting the proof and verifying it against the receipt key provided by the packet handler.
+`proofHeight: Number`: This is the height of the counterparty chain from which the proof was generated. A corresponding consensus state for this height must exist on the source client for the proof to verify correctly.
+
+TimeoutPacket Preconditions:
+
+- A valid client exists on the sending chain with the `sourceClientId`
+- There exists a mapping on the sending chain from `sourceClientId` to `Counterparty`
+- A packet commitment has been stored under the ICS24 packet path with `sourceClientId` and `sequence`
+
+TimeoutPacket Postconditions:
+
+- All sending applications execute the timeoutPacket logic with the payload.
+- Stored commitment for the packet is deleted
+
+TimeoutPacket Errorconditions:
+
+- `packet.destClient` != `counterparty.ClientId`. This should never happen if the second error condition is not true, since we constructed the packet correctly earlier
+- The packet provided by the relayer does not commit to the stored commitment we have stored for the `sourceClientId` and `sequence`
+- Non-Membership proof of the packet receipt on the receiving chain as standardized by ICS24 does not verify
+- Any of the applications return an error during the `TimeoutPacket` callback for their payload. Applications should generally not error on TimeoutPacket. If this occurs, it is most likely a bug so the error should revert the transaction and allow for the bug to be patched before resubmitting the transaction.
diff --git a/ibc/next/spec/IBC_V2/core/ics-005-port-allocation/README.mdx b/ibc/next/spec/IBC_V2/core/ics-005-port-allocation/README.mdx
new file mode 100644
index 00000000..57050290
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-005-port-allocation/README.mdx
@@ -0,0 +1,59 @@
+---
+ics: 5
+title: Port Allocation
+stage: draft
+category: IBC/TAO
+kind: interface
+required_by: [4]
+version_compatibility:
+ - ibc-go v10.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2024-05-17
+---
+
+## Synopsis
+
+This standard specifies the port allocation system by which modules can bind to uniquely named ports allocated by the IBC handler.
+The port identifiers in the packet defines which application to route the packet callback to. The source portID is an identifier of the application sending the packet, thus it will also receive the `AcknowledgePacket` and `TimeoutPacket` callback. The destination portID is the identifier of the application receiving the packet and will receive the `ReceivePacket` callback.
+
+Modules may register multiple ports on a state machine and send from any of their registered ports to any arbitrary port on a remote state machine. Each port on a state machine must be mapped to a specific IBC module as defined by [ICS-26](../ics-026-application-callbacks/README). Thus the IBC application to portID mapping is one-to-many.
+
+NOTE: IBC v1 included a channel along with a channel handshake that explicitly associated a unique channel between two portIDs on counterparty chains. Thus, the portIDs on both sides were tightly coupled such that no other application other than the ones bound by the portIDs were allowed to send packets on the dedicated channel. IBC v2 removed the concept of a channel and all packet flow is between chains rather than being isolated module-module communication. Thus, an application on a sending chain is allowed to send a packet to ANY other application on a destination chain by identifying the application with the portIDs in the packet. Thus, it is now the responsibility of applications to restrict which applications are allowed to send packets to them by checking the portID in the callback and rejecting any packet that comes from an unauthorized application.
+
+### Motivation
+
+The interblockchain communication protocol is designed to facilitate module-to-module communication, where modules are independent, possibly mutually distrusted, self-contained
+elements of code executing on sovereign ledgers.
+
+## Technical Specification
+
+### Registering a port
+
+The IBC handler MUST provide a way for applications to register their callbacks on a portID.
+
+```typescript
+function registerPort(portId: Identifier, cbs: ICS26App) => void
+```
+
+RegisterPort Preconditions:
+
+- There is no other application that is registered on the port router for the given `portId`.
+
+RegisterPort Postconditions:
+
+- The ICS26 application is registered on the provided `portId`.
+- Any incoming packet flow message addressed to the `portId` is routed to the ICS26 application. Any outgoing packet flow message addressed by the `portId` MUST come from the ICS26 application
+
+### Authenticating and Routing Packet Flow Messages
+
+Once an application is registered with a port, it is the port router's responsibility to properly route packet flow messages to the appropriate application identified by the portId in the payload. Similarly when the application sends packet flow messages to the port router, the router MUST ensure that the application is authenticated to send the packet flow message by checking if the payload portIDs are registered to the application.
+
+For packet flow messages on the packet sending chain (e.g. `SendPacket`, `AcknowledgePacket`, `TimeoutPacket`); the port router MUST do this authentication and routing using the packet payload's `sourcePortId`.
+
+For packet flow messages on the packet receiving chain (e.g. `RecvPacket` and optionally the asynchronous `WriteAcknowledgement`); the port router MUST do this authentication and routing using the packet payload's `destPortId`.
+
+[ICS-4](../ics-004-packet-semantics/PACKET_HANDLER) defines the packet flow messages and the expected behavior of their respected handlers. When the packet flow message arrives from the core ICS-4 handler to the application (e.g. `RecvPacket`, `AcknowledgePacket`, `TimeoutPacket`); then the portRouter acts as a router routing the message from the core handler to the ICS26 application. When the packet flow message arrives from the application to the core ICS-4 handler (e.g. `SendPacket`, or the optional `WriteAcknowledgement`); then the portRouter acts as an authenticator by checking that the calling application is registered as the owner of port they wish to send the message on before sending the message to the ICS-4 handler.
+
+NOTE: It is possible for implementations to change the order of execution flow so long as they still respect all the expected semantics and behavior defined in ICS-4. In this case, the port router's role as router or authenticator will change accordingly.
diff --git a/ibc/next/spec/IBC_V2/core/ics-024-host-requirements/README.mdx b/ibc/next/spec/IBC_V2/core/ics-024-host-requirements/README.mdx
new file mode 100644
index 00000000..4d9ba4d5
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-024-host-requirements/README.mdx
@@ -0,0 +1,148 @@
+---
+ics: 24
+title: Host State Machine Requirements
+stage: draft
+category: IBC/TAO
+kind: interface
+required_by: [4]
+version_compatibility:
+ - ibc-go v10.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2024-08-21
+modified: 2024-08-21
+---
+
+
+## Synopsis
+
+This specification defines the minimal set of properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol. IBC relies on a key-value provable store for cross-chain communication. In version 2 of the specification, the expected key-value storage will only be for the keys that are relevant for packet processing.
+
+### Motivation
+
+IBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host.
+
+### Definitions
+
+### Desired Properties
+
+IBC should require as simple an interface from the underlying state machine as possible to maximise the ease of correct implementation.
+
+## Technical Specification
+
+### Module system
+
+The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a "master module" or execution environment.
+
+The IBC core handlers as defined in ICS-4 must have
+
+### Paths, identifiers, separators
+
+An `Identifier` is a bytestring used as a key for an object stored in state, such as a packet commitment, acknowledgement, or receipt.
+
+Identifiers MUST be non-empty (of positive integer length).
+
+Identifiers MUST consist of characters in one of the following categories only:
+
+- Alphanumeric
+- `.`, `_`, `+`, `-`, `#`
+- `[`, `]`, `<`, `>`
+
+A `Path` is a bytestring used as the key for an object stored in state. Paths MUST contain only identifiers, constant bytestrings, and the separator `"/"`.
+
+Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation MAY be implemented, but particular restrictions are not imposed by this specification.
+
+The separator `"/"` is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers MUST NOT contain the `"/"` character, which prevents ambiguity.
+
+By default, identifiers have the following minimum and maximum lengths in characters:
+
+| Port identifier | Client identifier |
+| --------------- | ----------------- |
+| 2 - 128 | 2 - 64 |
+
+### Key/value Store
+
+The host state machine MUST provide a key/value store interface
+with three functions that behave in the standard way:
+
+```typescript
+type get = (path: Path) => Value | void
+```
+
+```typescript
+type set = (path: Path, value: Value) => void
+```
+
+```typescript
+type queryProof = (path: Path) => (CommitmentProof, Value)
+```
+
+`queryProof` will return a `Membership` proof if there exists a value for that path in the key/value store and a `NonMembership` proof if there is no value stored for the path.
+
+The host state machine SHOULD provide an interface for deleting
+a Path from the key/value store as well though it is not required:
+
+```typescript
+type delete = (path: Path) => void
+```
+
+`Path` is as defined above. `Value` is an arbitrary bytestring encoding of a particular data structure. The specific Path and Values required to be written to the provable store are defined in [ICS-4](../ics-004-packet-semantics/PACKET).
+
+These functions MUST be permissioned to the IBC packet handler module (the implementation of which is described in [ICS-4](../ics-004-packet-semantics/PACKET_HANDLER)) only, so only the IBC handler module can `set` or `delete` the paths that can be read by `get`.
+
+In most cases, this will be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine. This is why ICS-2 defines a `counterpartyCommitmentPrefix` that is associated with the client. The IBC handler will prefix the `counterpartyCommitmentPrefix` to the ICS-4 standardized path before proof verification against a `ConsensusState` in the client.
+
+### Provable Path-space
+
+IBC/TAO implementations MUST implement the following paths for the `provableStore` in the exact format specified. This is because counterparty IBC/TAO implementations will construct the paths according to this specification and send it to the light client to verify the IBC specified value stored under the IBC specified path.
+
+Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler.
+
+| Value | Path format |
+| -------------------------- | -------------------------------------------- |
+| Packet Commitment | {sourceClientId}0x1{bigEndianUint64Sequence} |
+| Packet Receipt | {destClientId}0x2{bigEndianUint64Sequence} |
+| Acknowledgement Commitment | {destClientId}0x3{bigEndianUint64Sequence} |
+
+IBC V2 only proves commitments related to packet handling, thus the commitments and how to construct them are specifed in [ICS-4](../ics-004-packet-semantics/PACKET).
+
+As mentioned above, the provable path space controlled by the IBC handler may be prefixed in a global provable key/value store. In this case, the prefix must be appended by the IBC handler before the proof is verified.
+
+The provable store MUST be capable of providing `MembershipProof` for a key/value pair that exists in the store. It MUST also be capable of providing a `NonMembership` proof for a key that does not exist in the store.
+
+In the case, the state machine does not support `NonMembership` proofs; a client may get around this restriction by associating a `SENTINEL_ABSENCE_VALUE` with meaning the key does not exist and treating a `MembershipProof` with a `SENTINEL_ABSENCE_VALUE` as a `NonMembershipProof`. In this case, the state machine is responsible for ensuring that there is a way to write a `SENTINEL_ABSENCE_VALUE` to the keys that IBC needs to prove nonmembership for and it MUST ensure that an actor cannot set the `SENTINEL_ABSENCE_VALUE` directly for a key accidentally. These requirements and how to implement them are outside the scope of this specification and remain the responsibility of the bespoke IBC implementation.
+
+### Finality
+
+The state machine MUST make updates sequentially so that all state updates happen in order and can be associated with a unique `Height` in that order. Each state update at a height `h` MUST be eventually **finalized** at a finite timestamp `t` such that the order of state updates from the initial state up to `h` will never change after time `t`.
+
+IBC handlers will only accept packet-flow messages from state updates which are already deemed to be finalized. In cases where the finality property is probabilistically guaranteed, this probabilitic guarantee must be handled within the ICS-2 client in order to provide a final view of the remote state machine for the ICS-4 packet handler.
+
+### Time
+
+As the state updates are applied to the state machine over time, the state update algorithm MUST itself have secure access to the current timestamp at which the state update is being applied. This is needed for IBC handlers to process timeouts correctly.
+
+If the state machine update mechanism does not itself provide a timestamp to the state machine handler, then there must be a time oracle updates as part of the state machine update itself. In this case, the security model of IBC will also include the security model of the time oracle.
+
+This timestamp for a state update MUST be monotonically increasing and it MUST be the greater than or equal to the timestamp that the counterparty client will return for the `ConsensusState` associated with that state update.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Key/value store functionality and consensus state type are unlikely to change during operation of a single host state machine.
+
+`submitDatagram` can change over time as relayers should be able to update their processes.
+
+## Example Implementations
+
+## History
+
+Aug 21, 2024 - [Initial draft](https://github.com/cosmos/ibc/pull/1144)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/IBC_V2/core/ics-026-application-callbacks/README.mdx b/ibc/next/spec/IBC_V2/core/ics-026-application-callbacks/README.mdx
new file mode 100644
index 00000000..adb92786
--- /dev/null
+++ b/ibc/next/spec/IBC_V2/core/ics-026-application-callbacks/README.mdx
@@ -0,0 +1,184 @@
+---
+ics: 26
+title: IBC Application Callbacks
+stage: draft
+category: IBC/TAO
+kind: instantiation
+version_compatibility:
+ - ibc-go v10.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2025-03-19
+---
+
+
+## Synopsis
+
+IBC enables module to module communication across remote state machines by providing a secure packet flow authenticated by the ICS-4 packet handler. The IBC core protocol is responsible for TAO (transport, authentication, ordering) of packets between two chains. These packets contain payload(s) that carry the application-specific information that is being communicated between two ICS26 applications. The data in the payload is itself opaque to the IBC core protocol, IBC core only verifies that it was correctly sent by the sender and then provides that data to the receiver for application-specific interpretation and processing.
+
+This specification standardizes the interface between ICS-4 (core IBC/TAO) and an IBC application (i.e. ICS26 app) for all the packet flow messages.
+
+The default IBC handler uses a receiver call pattern, where modules must individually call the IBC handler in order to send packets. In turn, the IBC handler verifies incoming packet flow messages like `ReceivePacket`, `AcknowledgePacket` and `TimeoutPacket` and calls into the appropriate ICS26 application as described in [ICS5 Port Allocation](../ics-005-port-allocation/README).
+
+## Technical Specification
+
+### Payload Structure
+
+The payload structure is reproduced from [ICS-4](../ics-004-packet-semantics/PACKET) since all of the following application functions are operating on the payloads that are being sent in the packets.
+
+```typescript
+interface Payload {
+ sourcePort: bytes, // identifier of the sending application on the sending chain
+ destPort: bytes, // identifier of the receiving application on the receiving chain
+ version: string, // payload version only interpretable by sending/receiving applications
+ encoding: string, // payload encoding only interpretable by sending/receiving applications
+ value: bytes // application-specific data that can be parsed by receiving application given the version and encoding
+}
+```
+
+### Core Handler Interface Exposed to ICS26 Applications
+
+The IBC core handler MUST expose the following function signature to the ICS26 applications registered on the port router, so that the application can send packets.
+
+#### SendPacket
+
+SendPacket Inputs:
+
+`payloads: Payload`: This is the payload that the application wishes to send to an application on the receiver chain.
+`sourceClientId: bytes`: Identifier of the receiver chain client that exists on the sending chain.
+`timeoutTimestamp: uint64`: The timeout in UNIX seconds after which the packet is no longer receivable on the receiving chain. NOTE: This timestamp is evaluated against the **receiving chain** clock as there may be drift between the sending chain and receiving chain clocks
+
+SendPacket Preconditions:
+
+- The application is registered on the port router with `payload.SourcePortId`
+- The application MUST have successfully conducted any application specific logic necessary for sending the given payload.
+- The sending client exists for `sourceClientId`
+
+SendPacket Postconditions:
+
+- The following packet gets committed and stored under the packet commitment path as specified by ICS24:
+
+```typescript
+interface Packet {
+ sourceClientId: sourceClientId,
+ destClientId: getCounterparty(sourceClientId).ClientId, // destClientId should be filled in with the registered counterparty id for provided sourceClientId
+ sequence: generateUniqueSequence(sourceClientId),
+ timeoutTimestamp: msg.timeoutTimestamp
+ data: msg.Payloads
+}
+```
+
+- The sequence is returned to the ICS26 application
+
+SendPacket ErrorConditions:
+
+- The sending client is invalid (expired or frozen)
+- The provided `timeoutTimstamp` has already elapsed
+- The sending application is not allowed to send the provided payload to the requested receiving application as identified by `payload.DestPort`
+
+NOTE: IBC v2 allows multiple payloads coming from multiple applications to be sent in the same packet. If an implementation chooses to support this feature, they may either provide an entrypoint in the core handler to send multiple packets, which must then call each individual application `OnSendPacket` callback to validate their individual payload and do application-specific sending logic; or they may queue the payloads coming from each application until the packet is ready to be committed.
+
+#### WriteAcknowledgement
+
+The IBC core handler MAY expose the following function signature to the ICS26 applications registed on the port router, so that the application can write acknowledgements asynchronously.
+
+This is only necessary if the implementation supports processing packets asynchronously. In this case, an application may process the packet asynchronously from when the IBC core handler receives the packet. Thus, the acknowledgement cannot be returned as part of the `OnRecvPacket` callback and must be submitted to the core IBC handler by the ICS26 application at a later time. Thus, we must introduce a new endpoint on the IBC handler for the ICS26 application to call when it is done processing a receive packet and wants to write the acknowledgement.
+
+WriteAcknowledgement Inputs:
+
+`destClientId: bytes`: Identifier of the sender chain client that exist on the receiving chain (i.e. executing chain)
+`sequence: uint64`: Unique sequence identifying the packet from sending chain to receiving chain
+`ack: bytes`: Acknowledgement from the receiving application for the payload it was sent by the application. If the receive was unsuccessful, the `ack` must be the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, otherwise it may be some application-specific data.
+
+WriteAcknowledgement Preconditions:
+
+- A packet receipt is stored under the specified ICS24 with the `destClientId` and `sequence`
+- An acknowledgement for the `destClientId` and `sequence` has not already been written under the ICS24 path
+
+WriteAcknowledgement Postconditions:
+
+- The acknowledgement is committed and written to the acknowledgement path as specified in ICS24
+- If the acknowledgement is successful, then all receiving applications must have executed their recvPacket logic and written state
+- If the acknowledgement is unsuccessful (ie ERROR ACK), any state changes made by the receiving applications MUST all be reverted. This ensure atomic execution of the multi-payload packet.
+
+NOTE: In the case that the packet contained multiple payloads, the IBC core handler MUST wait for all applications to return their individual acknowledgements for the packet before commiting the acknowledgment. If ANY application returns the error acknowledgement, then the acknowledgement for the entire packet only contains the `ERROR_SENTINEL_ACKNOWLEDGEMENT`. Otherwise, the acknowledgment is a list containing each applications individual acknowledgment in the same order that their associated payload existed in the packet.
+
+### ICS26 Interface Exposed to Core Handler
+
+Modules must expose the following function signatures to the routing module, which are called upon the receipt of various datagrams:
+
+#### OnRecvPacket
+
+OnRecvPacket Inputs:
+
+`sourceClientId: bytes`: This is the identifier of the client on the sending chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain.
+`destClientId: bytes`: This is the identifier of the receiving chain (i.e. executing chain)
+`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(destClientId, sequence)` uniquely identifies the packet on this chain.
+`payload: Payload`. This is the payload that an application registered by `payload.SourcePort` on the sending chain sends to the executing application
+
+OnRecvPacket Preconditions:
+
+- The application is registered on the port router with `payload.DestPort`
+- The destination client exists for `destClientId`
+- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives a packet; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given payload.
+
+OnRecvPacket Postconditions:
+
+- The application has executed all app-specific logic for the given payload and made the appropriate state changes
+- The application returns an app acknowledgment `ack: bytes` to the core IBC handler to be written as an acknowledgement of the payload in this packet.
+
+OnRecvPacket ErrorConditions:
+
+- The sending application as identified by `payload.SourcePortId` is not allowed to send a payload to the receiving application
+- The requested version as identified by `payload.Version` is unsupported
+- The requested encoding as identified by `payload.Encoding` is unsupported
+- An error occured while processing the `payload.Value` after decoding with `payload.Encoding` and processing the payload in the manner expected by `payload.Version`.
+
+IMPORTANT: If the `OnRecvPacket` callback errors for any reason, the state changes made during the callback MUST be reverted and the IBC core handler MUST write the `SENTINEL_ERROR_ACKNOWLEDGEMENT` for this packet even if other payloads in the packet are received successfully.
+
+#### OnAcknowledgePacket
+
+OnAcknowledgePacket Inputs:
+
+`sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain).
+`destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain.
+`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain.
+`acknowledgement: bytes`: This is the acknowledgement that the receiving application sent for the payload that we previously sent. It may be a successful acknowledgement with app-specific information or it may be the `SENTINEL_ERROR_ACKNOWLEDGEMENT` in which case we should handle any app-specific logic needed for a packet that failed to be sent.
+`payload: Payload`: This is the original payload that we previously sent
+
+OnAcknowledgementPreconditions:
+
+- This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`.
+- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an acknowledgement; it can be guaranteed of its authenticity and need only perform the relevant application logic for the given acknowledgement and payload.
+
+OnAcknowledgement Postconditions:
+
+- The application has executed all app-specific logic for the given payload and acknowledgment and made the appropriate state changes
+- If the acknowledgement was the `SENTINEL_ERROR_ACKNOWLEDGEMENT`, this will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer)
+
+OnAcknowledgement Errorconditions:
+
+- Application specific errors may occur while processing the acknowledgement. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not.
+
+#### OnTimeoutPacket
+
+OnTimeoutPacket Inputs:
+
+`sourceClientId: bytes`: This is the identifier of the client on the sending chain (i.e. executing chain).
+`destClientId: bytes`: This is the identifier of the receiving chain. NOTE: This is an identifier on the counterparty chain provided as information for the application, but it should not be treated as a unique identifier on the receiving chain.
+`sequence: uint64`: This is the unique sequence for the packet in the stream of packets from sending chain to destination chain. The tuple `(sourceClientId, sequence)` uniquely identifies the packet on this chain.
+`payload: Payload`: This is the original payload that we previously sent
+
+OnTimeoutPacket Preconditions:
+
+- This application had previously sent the provided payload in a packet with the provided `sourceClientId` and `sequence`.
+- All IBC/TAO verification checks have already been authenticated by IBC core handler. Thus, when the application receives an timeout; it can be guaranteed of its authenticity and need only perform the relevant application timeout logic for the given payload.
+
+OnTimeoutPacket Postconditions:
+
+- The application has executed all app-specific logic for the given payload and made the appropriate state changes. This will usually involve reverting whatever application state changes were made during `SendPacket` (e.g. unescrowing tokens for transfer)
+
+OnTimeoutPacket Errorconditions:
+
+- Application specific errors may occur while processing the timeout. The packet lifecycle is already complete. Implementations MAY choose to allow retries or not.
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/README.mdx b/ibc/next/spec/app/ics-020-fungible-token-transfer/README.mdx
new file mode 100644
index 00000000..577c5392
--- /dev/null
+++ b/ibc/next/spec/app/ics-020-fungible-token-transfer/README.mdx
@@ -0,0 +1,452 @@
+---
+ics: 20
+title: Fungible Token Transfer
+version: 1
+stage: draft
+category: IBC/APP
+requires: [25, 26]
+kind: instantiation
+version_compatibility:
+ - ibc-go v10.0.0
+ - ibc-rs v0.53.0
+author:
+ name: Christopher Goes
+ email: cwgoes@interchain.berlin
+created: 2019-07-15
+modified: 2020-02-24
+---
+
+
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of fungible tokens over an IBC channel between two modules on separate chains. The state machine logic presented allows for safe multi-chain denomination handling with permissionless channel opening. This logic constitutes a "fungible token transfer bridge module", interfacing between the IBC routing module and an existing asset tracking module on the host state machine.
+
+### Motivation
+
+Users of a set of chains connected over the IBC protocol might wish to utilise an asset issued on one chain on another chain, perhaps to make use of additional features such as exchange or privacy protection, while retaining fungibility with the original asset on the issuing chain. This application-layer standard describes a protocol for transferring fungible tokens between chains connected with IBC which preserves asset fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning.
+
+### Definitions
+
+The IBC handler interface & IBC routing module interface are as defined in [ICS 25](../../core/ics-025-handler-interface) and [ICS 26](../../core/ics-026-routing-module), respectively.
+
+### Desired Properties
+
+- Preservation of fungibility (two-way peg).
+- Preservation of total supply (constant or inflationary on a single source chain & module).
+- Permissionless token transfers, no need to whitelist connections, modules, or denominations.
+- Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones).
+- Fault containment: prevents Byzantine-inflation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behaviour (though any users who sent tokens to chain `B` may be at risk).
+
+## Technical Specification
+
+### Data Structures
+
+Only one packet data type is required: `FungibleTokenPacketData`, which specifies the denomination, amount, sending account, and receiving account.
+
+```typescript
+interface FungibleTokenPacketData {
+ denom: string
+ amount: uint256
+ sender: string
+ receiver: string
+ memo: string
+}
+```
+
+As tokens are sent across chains using the ICS 20 protocol, they begin to accrue a record of channels for which they have been transferred across. This information is encoded into the `denom` field.
+
+The ICS 20 token denominations are represented by the form `{ics20Port}/{ics20Channel}/{denom}`, where `ics20Port` and `ics20Channel` are an ICS 20 port and channel on the current chain for which the funds exist. The prefixed port and channel pair indicate which channel the funds were previously sent through. Implementations are responsible for correctly parsing the IBC trace information from the base denomination. The way the reference ICS 20 implementation in ibc-go handles this is by taking advantage of the fact that it automatically generates channel identifiers with the format `channel-{n}`, where `n` is a integer greater or equal than 0. It can then correctly parse out the IBC trace information from the base denom which may have slashes, but will not have a substring of the form `{transfer-port-name}/channel-{n}`. If this assumption is broken, the trace information will be parsed incorrectly (i.e. part of the base denom will be misinterpreted as trace information). Thus chains must make sure that base denominations do not have the ability to create arbitrary prefixes that can mock the ICS 20 logic.
+
+A sending chain may be acting as a source or sink zone. When a chain is sending tokens across a port and channel which are not equal to the last prefixed port and channel pair, it is acting as a source zone. When tokens are sent from a source zone, the destination port and channel will be prefixed onto the denomination (once the tokens are received) adding another hop to a tokens record. When a chain is sending tokens across a port and channel which are equal to the last prefixed port and channel pair, it is acting as a sink zone. When tokens are sent from a sink zone, the last prefixed port and channel pair on the denomination is removed (once the tokens are received), undoing the last hop in the tokens record. A more complete explanation is present in the [ibc-go implementation](https://github.com/cosmos/ibc-go/blob/457095517b7832c42ecf13571fee1e550fec02d0/modules/apps/transfer/keeper/relay.go#L18-L49) and the [ADR 001](https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md).
+
+The following sequence diagram exemplifies the multi-chain token transfer dynamics. This process encapsulates the steps involved in transferring tokens in a cycle that begins and ends on the same chain, traversing through Chain A, Chain B, and Chain C. The order of operations is outlined as `A -> B -> C -> A -> C -> B -> A`.
+
+```mermaid
+sequenceDiagram
+ Note over chain A,chain B: A is source zone: A -> B
+ chain A->>chain A: Lock (escrow) tokens ("denom")
+ chain A->>chain B: Send transfer packet with tokens ("denom")
+ chain B->>chain B: Mint vouchers ("transfer/ChannelToA/denom")
+ Note over chain B,chain C: B is source zone: B -> C
+ chain B->>chain B: Lock (escrow) vouchers ("transfer/ChannelToA/denom")
+ chain B->>chain C: Send transfer packet with vouchers ("transfer/ChannelToA/denom")
+ chain C->>chain C: Mint vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain A,chain C: C is source zone: C -> A
+ chain C->>chain C: Lock (escrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain A: Mint vouchers ("tansfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain A,chain C: A is sink zone: A -> C
+ chain A->>chain A: Burn vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain C: Send transfer packet with vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain C: Unlock (unescrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain B,chain C: C is sink zone: C -> B
+ chain C->>chain C: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain B: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain B->>chain B: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom")
+ Note over chain B,chain A: B is sink zone: B -> A
+ chain B->>chain B: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain B->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain A: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom")
+```
+
+The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any).
+
+```typescript
+type FungibleTokenPacketAcknowledgement = FungibleTokenPacketSuccess | FungibleTokenPacketError;
+
+interface FungibleTokenPacketSuccess {
+ // This is binary 0x01 base64 encoded
+ result: "AQ=="
+}
+
+interface FungibleTokenPacketError {
+ error: string
+}
+```
+
+Note that both the `FungibleTokenPacketData` as well as `FungibleTokenPacketAcknowledgement` must be JSON-encoded (not Protobuf encoded) when they serialized into packet data. Also note that `uint256` is string encoded when converted to JSON, but must be a valid decimal number of the form `[0-9]+`.
+
+The fungible token transfer bridge module tracks escrow addresses associated with particular channels in state. Fields of the `ModuleState` are assumed to be in scope.
+
+```typescript
+interface ModuleState {
+ channelEscrowAddresses: Map
+}
+```
+
+### Sub-protocols
+
+The sub-protocols described herein should be implemented in a "fungible token transfer bridge" module with access to a bank module and to the IBC routing module.
+
+#### Port & channel setup
+
+The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module).
+
+```typescript
+function setup() {
+ capability = routingModule.bindPort("transfer", ModuleCallbacks{
+ onChanOpenInit,
+ onChanOpenTry,
+ onChanOpenAck,
+ onChanOpenConfirm,
+ onChanCloseInit,
+ onChanCloseConfirm,
+ onRecvPacket,
+ onTimeoutPacket,
+ onAcknowledgePacket,
+ onTimeoutPacketClose
+ })
+ claimCapability("port", capability)
+}
+```
+
+Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the fungible token transfer module on separate chains.
+
+An administrator (with the permissions to create connections & channels on the host state machine) is responsible for setting up connections to other state machines & creating channels
+to other instances of this module (or another module supporting this interface) on other chains. This specification defines packet handling semantics only, and defines them in such a fashion
+that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time.
+
+#### Routing module callbacks
+
+##### Channel lifecycle management
+
+Both machines `A` and `B` accept new channels from any module on another machine, if and only if:
+
+- The channel being created is unordered.
+- The version string is `ics20-1`.
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string) => (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics20-1" or empty
+ // if empty, we return the default transfer version to core IBC
+ // as the version for this channel
+ abortTransactionUnless(version === "ics20-1" || version === "")
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
+ return "ics20-1", nil
+}
+```
+
+```typescript
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) => (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics20-1"
+ abortTransactionUnless(counterpartyVersion === "ics20-1")
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
+ // return version that this chain will use given the
+ // counterparty version
+ return "ics20-1", nil
+}
+```
+
+```typescript
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ // port has already been validated
+ // assert that counterparty selected version is "ics20-1"
+ abortTransactionUnless(counterpartyVersion === "ics20-1")
+}
+```
+
+```typescript
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // accept channel confirmations, port has already been validated, version has already been validated
+}
+```
+
+```typescript
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // always abort transaction
+ abortTransactionUnless(FALSE)
+}
+```
+
+```typescript
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // no action necessary
+}
+```
+
+##### Packet relay
+
+In plain English, between chains `A` and `B`:
+
+- When acting as the source zone, the bridge module escrows an existing local asset denomination on the sending chain and mints vouchers on the receiving chain.
+- When acting as the sink zone, the bridge module burns local vouchers on the sending chains and unescrows the local asset denomination on the receiving chain.
+- When a packet times-out, local assets are unescrowed back to the sender or vouchers minted back to the sender appropriately.
+- Acknowledgement data is used to handle failures, such as invalid denominations or invalid destination accounts. Returning
+ an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain
+ to take appropriate action based on the nature of the failure.
+
+`sendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.
+
+```typescript
+function sendFungibleTokens(
+ denomination: string,
+ amount: uint256,
+ sender: string,
+ receiver: string,
+ sourcePort: string,
+ sourceChannel: string,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64, // in unix nanoseconds
+): uint64 {
+ prefix = "{sourcePort}/{sourceChannel}/"
+ // we are the source if the denomination is not prefixed
+ source = denomination.slice(0, len(prefix)) !== prefix
+ if source {
+ // determine escrow account
+ escrowAccount = channelEscrowAddresses[sourceChannel]
+ // escrow source tokens (assumed to fail if balance insufficient)
+ bank.TransferCoins(sender, escrowAccount, denomination, amount)
+ } else {
+ // receiver is source chain, burn vouchers
+ bank.BurnCoins(sender, denomination, amount)
+ }
+
+ // create FungibleTokenPacket data
+ data = FungibleTokenPacketData{denomination, amount, sender, receiver}
+
+ // send packet using the interface defined in ICS4
+ sequence = handler.sendPacket(
+ getCapability("port"),
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ json.marshal(data) // json-marshalled bytes of packet data
+ )
+
+ return sequence
+}
+```
+
+`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.
+
+```typescript
+function onRecvPacket(packet: Packet) {
+ FungibleTokenPacketData data = packet.data
+ assert(data.denom !== "")
+ assert(data.amount > 0)
+ assert(data.sender !== "")
+ assert(data.receiver !== "")
+
+ // construct default acknowledgement of success
+ FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null}
+ prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
+ // we are the source if the packets were prefixed by the sending chain
+ source = data.denom.slice(0, len(prefix)) === prefix
+ if source {
+ // receiver is source chain: unescrow tokens
+ // determine escrow account
+ escrowAccount = channelEscrowAddresses[packet.destChannel]
+ // unescrow tokens to receiver (assumed to fail if balance insufficient)
+ err = bank.TransferCoins(escrowAccount, data.receiver, data.denom.slice(len(prefix)), data.amount)
+ if (err !== nil)
+ ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
+ } else {
+ prefix = "{packet.destPort}/{packet.destChannel}/"
+ prefixedDenomination = prefix + data.denom
+ // sender was source, mint vouchers to receiver (assumed to fail if balance insufficient)
+ err = bank.MintCoins(data.receiver, prefixedDenomination, data.amount)
+ if (err !== nil)
+ ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
+ }
+ return ack
+}
+```
+
+`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.
+
+```typescript
+function onAcknowledgePacket(
+ packet: Packet,
+ acknowledgement: bytes) {
+ // if the transfer failed, refund the tokens
+ if (!acknowledgement.success)
+ refundTokens(packet)
+}
+```
+
+`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that it will not be received on the destination chain).
+
+```typescript
+function onTimeoutPacket(packet: Packet) {
+ // the packet timed-out, so refund the tokens
+ refundTokens(packet)
+}
+```
+
+`refundTokens` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original sender.
+
+```typescript
+function refundTokens(packet: Packet) {
+ FungibleTokenPacketData data = packet.data
+ prefix = "{packet.sourcePort}/{packet.sourceChannel}/"
+ // we are the source if the denomination is not prefixed
+ source = data.denom.slice(0, len(prefix)) !== prefix
+ if source {
+ // sender was source chain, unescrow tokens back to sender
+ escrowAccount = channelEscrowAddresses[packet.srcChannel]
+ bank.TransferCoins(escrowAccount, data.sender, data.denom, data.amount)
+ } else {
+ // receiver was source chain, mint vouchers back to sender
+ bank.MintCoins(data.sender, data.denom, data.amount)
+ }
+}
+```
+
+```typescript
+function onTimeoutPacketClose(packet: Packet) {
+ // can't happen, only unordered channels allowed
+}
+```
+
+#### Using the Memo Field
+
+Note: Since earlier versions of this specification did not include a `memo` field, implementations must ensure that the new packet data is still compatible with chains that expect the old packet data. A legacy implementation MUST be able to unmarshal a new packet data with an empty string memo into the legacy `FungibleTokenPacketData` struct. Similarly, an implementation supporting `memo` must be able to unmarshal a legacy packet data into the current struct with the `memo` field set to the empty string.
+
+The `memo` field is not used within transfer, however it may be used either for external off-chain users (i.e. exchanges) or for middleware wrapping transfer that can parse and execute custom logic on the basis of the passed in memo. If the memo is intended to be parsed and interpreted by higher-level middleware, then these middleware are advised to namespace their additions to the memo string so that they do not overwrite each other. Chains should ensure that there is some length limit on the entire packet data to ensure that the packet does not become a DOS vector. However, these do not need to be protocol-defined limits. If the receiver cannot accept a packet because of length limitations, this will lead to a timeout on the sender side.
+
+Memos that are intended to be read by higher level middleware for custom execution must be structured so that different middleware can read relevant data in the memo intended for them without interfering with data intended for other middlewares.
+
+Thus, for any memo that is meant to be interpreted by the state machine; it is recommended that the memo is a JSON object with each middleware reserving a key that it can read into and retrieve relevant data. This way the memo can be constructed to pass in information such that multiple middleware can read the memo without interference from each other.
+
+Example:
+
+```json
+{
+ "wasm": {
+ "address": "contractAddress",
+ "arguments": "marshalledArguments",
+ },
+ "callback": "contractAddress",
+ "router": "routerArgs",
+}
+```
+
+Here, the "wasm", "callback", and "router" fields are all intended for separate middlewares that will exclusively read those fields respectively in order to execute their logic. This allows multiple modules to read from the memo. Middleware should take care to reserve a unique key so that they do not accidentally read data intended for a different module. This issue can be avoided by some off-chain registry of keys already in-use in the JSON object.
+
+#### Reasoning
+
+##### Correctness
+
+This implementation preserves both fungibility & supply.
+
+Fungibility: If tokens have been sent to the counterparty chain, they can be redeemed back in the same denomination & amount on the source chain.
+
+Supply: Redefine supply as unlocked tokens. All send-recv pairs sum to net zero. Source chain can change supply.
+
+##### Multi-chain notes
+
+This specification does not directly handle the "diamond problem", where a user sends a token originating on chain A to chain B, then to chain D, and wants to return it through D -> C -> A — since the supply is tracked as owned by chain B (and the denomination will be "{portOnD}/{channelOnD}/{portOnB}/{channelOnB}/denom"), chain C cannot serve as the intermediary. It is not yet clear whether that case should be dealt with in-protocol or not — it may be fine to just require the original path of redemption (and if there is frequent liquidity and some surplus on both paths the diamond path will work most of the time). Complexities arising from long redemption paths may lead to the emergence of central chains in the network topology.
+
+In order to track all of the denominations moving around the network of chains in various paths, it may be helpful for a particular chain to implement a registry which will track the "global" source chain for each denomination. End-user service providers (such as wallet authors) may want to integrate such a registry or keep their own mapping of canonical source chains and human-readable names in order to improve UX.
+
+#### Optional addenda
+
+- Each chain, locally, could elect to keep a lookup table to use short, user-friendly local denominations in state which are translated to and from the longer denominations when sending and receiving packets.
+- Additional restrictions may be imposed on which other machines may be connected to & which channels may be established.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+This initial standard uses version "ics20-1" in the channel handshake.
+
+A future version of this standard could use a different version in the channel handshake,
+and safely alter the packet data format & packet handler semantics.
+
+## Example Implementations
+
+- Implementation of ICS 20 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 20 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Jul 15, 2019 - Draft written
+
+Jul 29, 2019 - Major revisions; cleanup
+
+Aug 25, 2019 - Major revisions, more cleanup
+
+Feb 3, 2020 - Revisions to handle acknowledgements of success & failure
+
+Feb 24, 2020 - Revisions to infer source field, inclusion of version string
+
+July 27, 2020 - Re-addition of source field
+
+Nov 11, 2022 - Addition of a memo field
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/README.mdx b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/README.mdx
new file mode 100644
index 00000000..27e6ece3
--- /dev/null
+++ b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/README.mdx
@@ -0,0 +1,841 @@
+---
+ics: 20
+title: Fungible Token Transfer
+version: 2
+stage: draft
+category: IBC/APP
+requires: [25, 26]
+kind: instantiation
+version_compatibility: null
+authors:
+ - name: Christopher Goes
+ email: cwgoes@interchain.berlin
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2019-07-15
+modified: 2024-03-05
+---
+
+
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of fungible tokens over an IBC channel between two modules on separate chains. The state machine logic presented allows for safe multi-chain denomination handling with permissionless channel opening. This logic constitutes a "fungible token transfer bridge module", interfacing between the IBC routing module and an existing asset tracking module on the host state machine.
+
+### Motivation
+
+Users of a set of chains connected over the IBC protocol might wish to utilise an asset issued on one chain on another chain, perhaps to make use of additional features such as exchange or privacy protection, while retaining fungibility with the original asset on the issuing chain. This application-layer standard describes a protocol for transferring fungible tokens between chains connected with IBC which preserves asset fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning.
+
+### Definitions
+
+The IBC handler interface & IBC routing module interface are as defined in [ICS 25](../../../core/ics-025-handler-interface) and [ICS 26](../../../core/ics-026-routing-module), respectively.
+
+### Desired Properties
+
+- Preservation of fungibility (two-way peg).
+- Preservation of total supply (constant or inflationary on a single source chain & module).
+- Permissionless token transfers, no need to whitelist connections, modules, or denominations.
+- Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones).
+- Fault containment: prevents Byzantine-inflation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behaviour (though any users who sent tokens to chain `B` may be at risk).
+
+## Technical Specification
+
+### Data Structures
+
+Only one packet data type is required: `FungibleTokenPacketData`, which specifies the denomination, amount, sending account, and receiving account or `FungibleTokenPacketDataV2` which specifies multiple tokens being sent between sender and receiver along with an optional forwarding path that can forward tokens further beyond the initial receiving chain. A v2 supporting chain can optionally convert a v1 packet for channels that are still on version 1.
+
+```typescript
+interface FungibleTokenPacketData {
+ denom: string
+ amount: uint256
+ sender: string
+ receiver: string
+ memo: string
+}
+
+interface FungibleTokenPacketDataV2 {
+ tokens: []Token
+ sender: string
+ receiver: string
+ memo: string
+ // a struct containing the list of next hops,
+ // determining where the tokens must be forwarded next,
+ // and the memo for the final hop
+ forwarding: Forwarding
+}
+
+interface Token {
+ denom: Denom
+ amount: uint256
+}
+
+interface Denom {
+ base: string // base denomination
+ trace: []Hop
+}
+
+interface Forwarding {
+ hops: []Hop
+ memo: string
+}
+
+interface Hop {
+ portId: string
+ channelId: string
+}
+```
+
+As tokens are sent across chains using the ICS 20 protocol, they begin to accrue a record of channels for which they have been transferred across. This information is encoded into the `trace` field in the token.
+
+The ICS 20 token traces are represented by a list of `ics20Port` and `ics20Channel` pairs, which are an ICS 20 port and channel on the current chain for which the funds exist. The port and channel pair indicate which channel the funds were previously sent through. Implementations are responsible for correctly parsing the IBC trace information and encoding it into the final on-chain denomination so that the same base denominations sent through different paths are not treated as being fungible.
+
+A sending chain may be acting as a source or sink zone. When a chain is sending tokens across a port and channel which are not equal to the last prefixed port and channel pair, it is acting as a source zone. When tokens are sent from a source zone, the destination port and channel will be prepended to the trace (once the tokens are received) adding another hop to a tokens record. When a chain is sending tokens across a port and channel which are equal to the last prefixed port and channel pair, it is acting as a sink zone. When tokens are sent from a sink zone, the first element of the trace, which was the last port and channel pair added to the trace is removed (once the tokens are received), undoing the last hop in the tokens record. A more complete explanation is [present in the ibc-go implementation](https://github.com/cosmos/ibc-go/blob/457095517b7832c42ecf13571fee1e550fec02d0/modules/apps/transfer/keeper/relay.go#L18-L49).
+
+The following sequence diagram exemplifies the multi-chain token transfer dynamics. This process encapsulates the steps involved in transferring tokens in a cycle that begins and ends on the same chain, traversing through chain A, chain B, and chain C. The order of operations is outlined as `A -> B -> C -> A -> C -> B -> A`.
+
+```mermaid
+sequenceDiagram
+ Note over chain A,chain B: A is source zone: A -> B
+ chain A->>chain A: Lock (escrow) tokens ("denom")
+ chain A->>chain B: Send transfer packet with tokens ("denom")
+ chain B->>chain B: Mint vouchers ("transfer/ChannelToA/denom")
+ Note over chain B,chain C: B is source zone: B -> C
+ chain B->>chain B: Lock (escrow) vouchers ("transfer/ChannelToA/denom")
+ chain B->>chain C: Send transfer packet with vouchers ("transfer/ChannelToA/denom")
+ chain C->>chain C: Mint vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain A,chain C: C is source zone: C -> A
+ chain C->>chain C: Lock (escrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain A: Mint vouchers ("tansfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain A,chain C: A is sink zone: A -> C
+ chain A->>chain A: Burn vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain C: Send transfer packet with vouchers ("transfer/ChannelToC/transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain C: Unlock (unescrow) vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ Note over chain B,chain C: C is sink zone: C -> B
+ chain C->>chain C: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain C->>chain B: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain B->>chain B: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom")
+ Note over chain B,chain A: B is sink zone: B -> A
+ chain B->>chain B: Burn vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain B->>chain A: Send transfer packet with vouchers ("transfer/ChannelToB/transfer/ChannelToA/denom")
+ chain A->>chain A: Unlock (unescrow) vouchers ("transfer/ChannelToA/denom")
+```
+
+The forwarding path in the `v2` packet tells the receiving chain where to send the tokens to next. This must be constructed as a list of portID/channelID pairs with each element concatenated as `portID/channelID`. This allows users to automatically route tokens through the interchain. A common usecase might be to unwind the trace of the tokens back to the original source chain before sending it forward to the final intended destination.
+
+Here are examples of the transfer packet data:
+
+```typescript
+
+// V1 example of transfer packet data
+FungibleTokenPacketData {
+ denom: "transfer/channel-1/transfer/channel-4/uatom",
+ amount: 500,
+ sender: cosmosexampleaddr1,
+ receiver: cosmosexampleaddr2,
+ memo: "exampleMemo",
+}
+
+// V2 example of transfer packet data
+FungibleTokenPacketDataV2 {
+ tokens: [
+ Token{
+ denom: Denom{
+ base: "uatom",
+ trace: [
+ Hop{
+ portId: "transfer",
+ channelId: "channel-1",
+ },
+ Hop{
+ portId: "transfer",
+ channelId: "channel-4",
+ }
+ ],
+ },
+ amount: 500,
+ },
+ Token{
+ denom: Denom{
+ base: "btc",
+ trace: [
+ Hop{
+ portId: "transfer",
+ channelId: "channel-3",
+ }
+ ],
+ },
+ amount: 7,
+ }
+ ],
+ sender: cosmosexampleaddr1,
+ receiver: cosmosexampleaddr2,
+ memo: "",
+ forwarding: {
+ hops: [
+ Hop{portId: "transfer", channelId: "channel-7"},
+ Hop{portId: "transfer", channelId: "channel-13"},
+ ],
+ memo: "swap: {...}"
+ }, // provide hops in order and the memo intended for final hop
+}
+```
+
+The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any).
+
+```typescript
+type FungibleTokenPacketAcknowledgement = FungibleTokenPacketSuccess | FungibleTokenPacketError;
+
+interface FungibleTokenPacketSuccess {
+ // This is binary 0x01 base64 encoded
+ result: "AQ=="
+}
+
+interface FungibleTokenPacketError {
+ error: string
+}
+```
+
+Note that both the `FungibleTokenPacketData` as well as `FungibleTokenPacketAcknowledgement` must be JSON-encoded (not Protobuf encoded) when they serialized into packet data. Also note that `uint256` is string encoded when converted to JSON, but must be a valid decimal number of the form `[0-9]+`.
+
+The fungible token transfer bridge module tracks escrow addresses associated with particular channels in state. Fields of the `ModuleState` are assumed to be in scope.
+
+```typescript
+interface ModuleState {
+ channelEscrowAddresses: Map
+ channelForwardingAddresses: Map
+}
+```
+
+### Store paths
+
+#### Packet forward path
+
+The `v2` packets that have non-empty forwarding information and should thus be forwarded, must be stored in the private store, so that an acknowledgement can be written for them when receiving an acknowledgement or timeout for the forwarded packet.
+
+```typescript
+function packetForwardPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
+ return "forwardedPackets/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
+}
+```
+
+### Sub-protocols
+
+The sub-protocols described herein should be implemented in a "fungible token transfer bridge" module with access to a bank module and to the IBC routing module.
+
+#### Port & channel setup
+
+The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module).
+
+```typescript
+function setup() {
+ capability = routingModule.bindPort("transfer", ModuleCallbacks{
+ onChanOpenInit,
+ onChanOpenTry,
+ onChanOpenAck,
+ onChanOpenConfirm,
+ onChanCloseInit,
+ onChanCloseConfirm,
+ onRecvPacket,
+ onTimeoutPacket,
+ onAcknowledgePacket,
+ onTimeoutPacketClose
+ })
+ claimCapability("port", capability)
+}
+```
+
+Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the fungible token transfer module on separate chains.
+
+An administrator (with the permissions to create connections & channels on the host state machine) is responsible for setting up connections to other state machines & creating channels
+to other instances of this module (or another module supporting this interface) on other chains. This specification defines packet handling semantics only, and defines them in such a fashion
+that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time.
+
+#### Routing module callbacks
+
+##### Channel lifecycle management
+
+Both machines `A` and `B` accept new channels from any module on another machine, if and only if:
+
+- The channel being created is unordered.
+- The version string is `ics20-1` or `ics20-2`.
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string) => (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics20-1" or "ics20-2" or empty
+ // if empty, we return the default transfer version to core IBC
+ // as the version for this channel
+ abortTransactionUnless(version === "ics20-2" || version === "ics20-1" || version === "")
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
+ if version == "" {
+ // default to latest supported version
+ return "ics20-2", nil
+ }
+ // If the version is not empty and is among those supported, we return the version
+ return version, nil
+}
+```
+
+```typescript
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) => (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics20-1" or "ics20-2"
+ abortTransactionUnless(counterpartyVersion === "ics20-1" || counterpartyVersion === "ics20-2")
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress(portIdentifier, channelIdentifier)
+ // return the same version as counterparty version so long as we support it
+ return counterpartyVersion, nil
+}
+```
+
+```typescript
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ // port has already been validated
+ // assert that counterparty selected version is the same as our version
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(counterpartyVersion === channel.version)
+}
+```
+
+```typescript
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // accept channel confirmations, port has already been validated, version has already been validated
+}
+```
+
+```typescript
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // always abort transaction
+ abortTransactionUnless(FALSE)
+}
+```
+
+```typescript
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // no action necessary
+}
+```
+
+##### Packet relay
+
+In plain English, between chains `A` and `B`:
+
+- When acting as the source zone, the bridge module escrows an existing local asset denomination on the sending chain and mints vouchers on the receiving chain.
+- When acting as the sink zone, the bridge module burns local vouchers on the sending chains and unescrows the local asset denomination on the receiving chain.
+- When a packet times-out, local assets are unescrowed back to the sender or vouchers minted back to the sender appropriately.
+- Acknowledgement data is used to handle failures, such as invalid denominations or invalid destination accounts. Returning
+ an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain
+ to take appropriate action based on the nature of the failure.
+
+Note: `constructOnChainDenom` is a helper function that will construct the local on-chain denomination for the bridged token. It **must** encode the trace and base denomination to ensure that tokens coming over different paths are not treated as fungible. The original trace and denomination must be retrievable by the state machine so that they can be passed in their original forms when constructing a new IBC path for the bridged token. The ibc-go implementation handles this by creating a local denomination: `hash(trace+base_denom)`.
+
+`sendFungibleTokens` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.
+
+```typescript
+function sendFungibleTokens(
+ tokens: []Token,
+ sender: string,
+ receiver: string,
+ memo: string,
+ forwarding: Forwarding,
+ sourcePort: string,
+ sourceChannel: string,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64, // in unix nanoseconds
+): uint64 {
+ // memo and forwarding cannot both be non-empty
+ abortTransactionUnless(memo != "" && forwarding != nil)
+ for token in tokens
+ onChainDenom = constructOnChainDenom(token.denom.trace, token.denom.base)
+ // if the token is not prefixed by our channel end's port and channel identifiers
+ // then we are sending as a source zone
+ if !isTracePrefixed(sourcePort, sourceChannel, token) {
+ // determine escrow account
+ escrowAccount = channelEscrowAddresses[sourceChannel]
+ // escrow source tokens (assumed to fail if balance insufficient)
+ bank.TransferCoins(sender, escrowAccount, onChainDenom, token.amount)
+ } else {
+ // receiver is source chain, burn vouchers
+ bank.BurnCoins(sender, onChainDenom, token.amount)
+ }
+ }
+
+ var dataBytes bytes
+ channel = provableStore.get(channelPath(sourcePort, sourceChannel))
+ // getAppVersion returns the transfer version that is embedded in the channel version
+ // as the channel version may contain additional app or middleware version(s)
+ transferVersion = getAppVersion(channel.version)
+ if transferVersion == "ics20-1" {
+ abortTransactionUnless(len(tokens) == 1)
+ token = tokens[0]
+ // abort if forwarding defined
+ abortTransactionUnless(forwarding == nil)
+ // create v1 denom of the form: port1/channel1/port2/channel2/port3/channel3/denom
+ v1Denom = constructOnChainDenom(token.denom.trace, token.denom.base)
+ // v1 packet data does not support forwarding fields
+ data = FungibleTokenPacketData{v1Denom, token.amount, sender, receiver, memo}
+ // JSON-marshal packet data into bytes
+ dataBytes = json.marshal(data)
+ } else if transferVersion == "ics20-2" {
+ // create FungibleTokenPacket data
+ data = FungibleTokenPacketDataV2{tokens, sender, receiver, memo, forwarding}
+ // protobuf-marshal packet data into bytes
+ dataBytes = protobuf.marshal(data)
+ } else {
+ // should never be reached as transfer version must be negotiated to be either
+ // ics20-1 or ics20-2 during channel handshake
+ abortTransactionUnless(false)
+ }
+
+ // send packet using the interface defined in ICS4
+ sequence = handler.sendPacket(
+ getCapability("port"),
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ dataBytes,
+ )
+
+ return sequence
+}
+```
+
+`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.
+
+Note: Function `parseICS20V1Denom` is a helper function that will take the full IBC denomination and extract the base denomination (i.e. native denomination in the chain of origin) and the trace information (if any) for the received token.
+
+```typescript
+function onRecvPacket(packet: Packet) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ // getAppVersion returns the transfer version that is embedded in the channel version
+ // as the channel version may contain additional app or middleware version(s)
+ transferVersion = getAppVersion(channel.version)
+ var tokens []Token
+ var sender string
+ var receiver string // address to send tokens to on this chain
+ var finalReceiver string // final intended address in forwarding case
+
+ if transferVersion == "ics20-1" {
+ FungibleTokenPacketData data = json.unmarshal(packet.data)
+ // convert full denom string to denom struct with base denom and trace
+ denom = parseICS20V1Denom(data.denom)
+ token = Token{
+ denom: denom
+ amount: data.amount
+ }
+ tokens = []Token{token}
+ sender = data.sender
+ receiver = data.receiver
+ } else if transferVersion == "ics20-2" {
+ FungibleTokenPacketDataV2 data = protobuf.unmarshal(packet.data)
+ tokens = data.tokens
+ sender = data.sender
+
+ // if we need to forward the tokens onward
+ // overwrite the receiver to temporarily send to the
+ // channel escrow address of the intended receiver
+ if len(data.forwarding.hops) > 0 {
+ // memo must be empty
+ abortTransactionUnless(data.memo == "")
+ if channelForwardingAddress[packet.destChannel] == "" {
+ channelForwardingAddress[packet.destChannel] = newAddress()
+ }
+ receiver = channelForwardingAddresses[packet.destChannel]
+ finalReceiver = data.receiver
+ } else {
+ receiver = data.receiver
+ }
+ } else {
+ // should never be reached as transfer version must be negotiated
+ // to be either ics20-1 or ics20-2 during channel handshake
+ abortTransactionUnless(false)
+ }
+
+ assert(sender !== "")
+ assert(receiver !== "")
+
+ // construct default acknowledgement of success
+ FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, null}
+
+ receivedTokens = []Token
+ for token in tokens {
+ assert(token.denom !== nil)
+ assert(token.amount > 0)
+
+ var onChainTrace []Hop
+ // we are the source if the packets were prefixed by the sending chain
+ // if the sender sends the tokens prefixed with their channel end's
+ // port and channel identifiers then we are receiving tokens we
+ // previously had sent to the sender, thus we are receiving the tokens
+ // as a source zone
+ if isTracePrefixed(packet.sourcePort, packet.sourceChannel, token) {
+ // since we are receiving back to source we remove the prefix from the trace
+ onChainTrace = token.trace[1:]
+ onChainDenom = constructOnChainDenom(onChainTrace, token.denom.base)
+ // receiver is source chain: unescrow tokens
+ // determine escrow account
+ escrowAccount = channelEscrowAddresses[packet.destChannel]
+ // unescrow tokens to receiver (assumed to fail if balance insufficient)
+ err = bank.TransferCoins(escrowAccount, receiver, onChainDenom, token.amount)
+ if (err != nil) {
+ ack = FungibleTokenPacketAcknowledgement{false, "transfer coins failed"}
+ // break out of for loop on first error
+ break
+ }
+ } else {
+ // since we are receiving to a new sink zone we prepend the prefix to the trace
+ prefixTrace = Hop{portId: packet.destPort, channelId: packet.destChannel}
+ onChainTrace = append([]Hop{prefixTrace}, token.denom.trace...)
+ onChainDenom = constructOnChainDenom(onChainTrace, token.denom.base)
+ // sender was source, mint vouchers to receiver (assumed to fail if balance insufficient)
+ err = bank.MintCoins(receiver, onChainDenom, token.amount)
+ if (err !== nil) {
+ ack = FungibleTokenPacketAcknowledgement{false, "mint coins failed"}
+ // break out of for loop on first error
+ break
+ }
+ }
+
+ // add the received token to the received tokens list
+ recvToken = Token{
+ denom: Denom{base: token.denom.base, trace: onChainTrace},
+ amount: token.amount,
+ }
+ receivedTokens = append(receivedTokens, recvToken)
+ }
+
+ // if there is an error ack return immediately and do not forward further
+ if !ack.Success() {
+ return ack
+ }
+
+ // if acknowledgement is successful and forwarding path set
+ // then start forwarding
+ if len(forwarding.hops) > 0 {
+ //check that next channel supports token forwarding
+ channel = provableStore.get(channelPath(forwarding.hops[0].portId, forwarding.hops[0].channelId))
+ if channel.version != "ics20-2" && len(forwarding.hops) > 1 {
+ ack = FungibleTokenPacketAcknowledgement(false, "next hop in path cannot support forwarding onward")
+ return ack
+ }
+ memo = ""
+ nextForwarding = Forwarding{
+ hops: forwarding.hops[1:]
+ memo: forwarding.memo
+ }
+ if len(forwarding.hops) == 1 {
+ // we're on the last hop, we can set memo and clear
+ // the next forwarding
+ memo = forwarding.memo
+ nextForwarding = nil
+ }
+ // send the tokens we received above to the next port and channel
+ // on the forwarding path
+ // and reduce the forwarding by the first element
+ packetSequence = sendFungibleTokens(
+ receivedTokens,
+ receiver, // sender of next packet
+ finalReceiver, // receiver of next packet
+ memo,
+ nextForwarding,
+ forwarding.hops[0].portId,
+ forwarding.hops[0].channelId,
+ Height{},
+ currentTime() + DefaultHopTimeoutPeriod,
+ )
+ // store packet for future sending ack
+ privateStore.set(packetForwardPath(forwarding.hops[0].portId, forwarding.hops[0].channelId, packetSequence), packet)
+ // use async ack until we get successful acknowledgement from further down the line.
+ return nil
+ }
+
+ return ack
+}
+```
+
+`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.
+
+```typescript
+function onAcknowledgePacket(
+ packet: Packet,
+ acknowledgement: bytes) {
+ // if the transfer failed, refund the tokens
+ // to the sender account. In case of a packet sent for a
+ // forwarded packet, the sender is the forwarding
+ // address for the destination channel of the forwarded packet.
+ if !(acknowledgement.success) {
+ refundTokens(packet)
+ }
+
+ // check if the packet that was sent is from a previously forwarded packet
+ prevPacket = privateStore.get(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+
+ if prevPacket != nil {
+ if acknowledgement.success {
+ FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{true, "forwarded packet succeeded"}
+ handler.writeAcknowledgement(
+ prevPacket,
+ ack,
+ )
+ } else {
+ // the forwarded packet has failed, thus the funds have been refunded to the forwarding address.
+ // we must revert the changes that came from successfully receiving the tokens on our chain
+ // before propogating the error acknowledgement back to original sender chain
+ revertInFlightChanges(packet, prevPacket)
+ // write error acknowledgement
+ FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{false, "forwarded packet failed"}
+ handler.writeAcknowledgement(
+ prevPacket,
+ ack,
+ )
+ }
+
+ // delete the forwarded packet that triggered sending this packet
+ privateStore.delete(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+ }
+}
+```
+
+`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that it will not be received on the destination chain).
+
+```typescript
+function onTimeoutPacket(packet: Packet) {
+ // the packet timed-out, so refund the tokens
+ // to the sender account. In case of a packet sent for a
+ // forwarded packet, the sender is the forwarding
+ // address for the destination channel of the forwarded packet.
+ refundTokens(packet)
+
+ // check if the packet sent is from a previously forwarded packet
+ prevPacket = privateStore.get(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+
+ if prevPacket != nil {
+ // the forwarded packet has failed, thus the funds have been refunded to the forwarding address.
+ // we must revert the changes that came from successfully receiving the tokens on our chain
+ // before propogating the error acknowledgement back to original sender chain
+ revertInFlightChanges(packet, prevPacket)
+ // write error acknowledgement
+ FungibleTokenPacketAcknowledgement ack = FungibleTokenPacketAcknowledgement{false, "forwarded packet timed out"}
+ handler.writeAcknowledgement(
+ prevPacket,
+ ack,
+ )
+
+ // delete the forwarded packet that triggered sending this packet
+ privateStore.delete(packetForwardPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+ }
+}
+```
+
+Given three chains and a transfer from chain A to chain C through chain B, the following diagrams summarize the core logic of the protocol regarding the handling of tokens in the middle chain, both for the success case (i.e. tokens received on chain C) and failure case (i.e. tokens cannot be received on chain C and an error acknowledgement is written):
+
+
+
+
+
+##### Helper functions
+
+```typescript
+// helper function that returns true if the first element of the trace of the
+// token is matches the provided portId and channelId; otherwise it returns false
+function isTracePrefixed(portId: string, channelId: string, token: Token) boolean {
+ trace = token.trace[0]
+ return trace.portId == portId && trace.channelId == channelId
+}
+```
+
+`refundTokens` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original sender.
+
+```typescript
+function refundTokens(packet: Packet) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ // getAppVersion returns the transfer version that is embedded in the channel version
+ // as the channel version may contain additional app or middleware version(s)
+ transferVersion = getAppVersion(channel.version)
+ if transferVersion == "ics20-1" {
+ FungibleTokenPacketData data = json.unmarshal(packet.data)
+ // convert full denom string to denom struct with base denom and trace
+ denom = parseICS20V1Denom(data.denom)
+ token = Token{
+ denom: denom
+ amount: data.amount
+ }
+ tokens = []Token{token}
+ } else if transferVersion == "ics20-2" {
+ FungibleTokenPacketDataV2 data = protobuf.unmarshal(packet.data)
+ tokens = data.tokens
+ } else {
+ // should never be reached as transfer version must be negotiated to be either
+ // ics20-1 or ics20-2 during channel handshake
+ abortTransactionUnless(false)
+ }
+
+ for token in tokens {
+ onChainDenom = constructOnChainDenom(token.denom.trace, token.denom.base)
+ // Since this is refunding an outgoing packet, we can check if the tokens
+ // were originally from the receiver by checking if the tokens were prefixed
+ // by our channel end's identifiers.
+ if !isTracePrefixed(packet.sourcePort, packet.sourceChannel, token) {
+ // sender was source chain, unescrow tokens back to sender
+ escrowAccount = channelEscrowAddresses[packet.sourceChannel]
+ bank.TransferCoins(escrowAccount, data.sender, onChainDenom, token.amount)
+ } else {
+ // receiver was source chain, mint vouchers back to sender
+ bank.MintCoins(data.sender, onChainDenom, token.amount)
+ }
+ }
+}
+```
+
+```typescript
+// revertInFlightChanges reverts the receive packet
+// that occurs in the middle chains during a packet forwarding
+// If an error occurs further down the line, the state changes
+// on this chain must be reverted before sending back the error acknowledgement
+// to ensure atomic packet forwarding
+function revertInFlightChanges(sentPacket: Packet, receivedPacket: Packet) {
+ forwardingAddress = channelForwardingAddress[receivedPacket.destChannel]
+ reverseEscrow = channelEscrowAddresses[receivedPacket.destChannel]
+
+ // the token on our chain is the token in the sentPacket
+ for token in sentPacket.tokens {
+ // we are checking if the tokens that were sent out by our chain in the
+ // sentPacket were source tokens with respect to the original receivedPacket.
+ // If the tokens in sentPacket were prefixed by our channel end's port and channel
+ // identifiers, then it was a minted voucher and we need to burn it.
+ // Otherwise, it was an original token from our chain and we must give the tokens
+ // back to the escrow account.
+ if !isTracePrefixed(receivedPacket.destinationPort, receivedPacket.destinationChannel, token) {
+ // receive sent tokens from the received escrow account to the forwarding account
+ // so we must send the tokens back from the forwarding account to the received escrow account
+ bank.TransferCoins(forwardingAddress, reverseEscrow, token.denom, token.amount)
+ } else {
+ // receive minted vouchers and sent to the forwarding account
+ // so we must burn the vouchers from the forwarding account
+ bank.BurnCoins(forwardingAddress, token.denom, token.amount)
+ }
+ }
+}
+```
+
+```typescript
+function onTimeoutPacketClose(packet: Packet) {
+ // can't happen, only unordered channels allowed
+}
+```
+
+#### Using the Memo Field
+
+Note: Since earlier versions of this specification did not include a `memo` field, implementations must ensure that the new packet data is still compatible with chains that expect the old packet data. A legacy implementation MUST be able to unmarshal a new packet data with an empty string memo into the legacy `FungibleTokenPacketData` struct. Similarly, an implementation supporting `memo` must be able to unmarshal a legacy packet data into the current struct with the `memo` field set to the empty string.
+
+The `memo` field is not used within transfer, however it may be used either for external off-chain users (i.e. exchanges) or for middleware wrapping transfer that can parse and execute custom logic on the basis of the passed in memo. If the memo is intended to be parsed and interpreted by higher-level middleware, then these middleware are advised to namespace their additions to the memo string so that they do not overwrite each other. Chains should ensure that there is some length limit on the entire packet data to ensure that the packet does not become a DOS vector. However, these do not need to be protocol-defined limits. If the receiver cannot accept a packet because of length limitations, this will lead to a timeout on the sender side.
+
+Memos that are intended to be read by higher level middleware for custom execution must be structured so that different middleware can read relevant data in the memo intended for them without interfering with data intended for other middlewares.
+
+Thus, for any memo that is meant to be interpreted by the state machine; it is recommended that the memo is a JSON object with each middleware reserving a key that it can read into and retrieve relevant data. This way the memo can be constructed to pass in information such that multiple middleware can read the memo without interference from each other.
+
+Example:
+
+```json
+{
+ "wasm": {
+ "address": "contractAddress",
+ "arguments": "marshalledArguments",
+ },
+ "callback": "contractAddress",
+ "router": "routerArgs",
+}
+```
+
+Here, the "wasm", "callback", and "router" fields are all intended for separate middlewares that will exclusively read those fields respectively in order to execute their logic. This allows multiple modules to read from the memo. Middleware should take care to reserve a unique key so that they do not accidentally read data intended for a different module. This issue can be avoided by some off-chain registry of keys already in-use in the JSON object.
+
+#### Reasoning
+
+##### Correctness
+
+This implementation preserves both fungibility & supply.
+
+Fungibility: If tokens have been sent to the counterparty chain, they can be redeemed back in the same denomination & amount on the source chain.
+
+Supply: Redefine supply as unlocked tokens. All send-recv pairs sum to net zero. Source chain can change supply.
+
+##### Multi-chain notes
+
+This specification does not directly handle the "diamond problem", where a user sends a token originating on chain A to chain B, then to chain D, and wants to return it through D -> C -> A — since the supply is tracked as owned by chain B (and the denomination will be "{portOnD}/{channelOnD}/{portOnB}/{channelOnB}/denom"), chain C cannot serve as the intermediary. It is not yet clear whether that case should be dealt with in-protocol or not — it may be fine to just require the original path of redemption (and if there is frequent liquidity and some surplus on both paths the diamond path will work most of the time). Complexities arising from long redemption paths may lead to the emergence of central chains in the network topology.
+
+In order to track all of the denominations moving around the network of chains in various paths, it may be helpful for a particular chain to implement a registry which will track the "global" source chain for each denomination. End-user service providers (such as wallet authors) may want to integrate such a registry or keep their own mapping of canonical source chains and human-readable names in order to improve UX.
+
+#### Optional addenda
+
+- Each chain, locally, could elect to keep a lookup table to use short, user-friendly local denominations in state which are translated to and from the longer denominations when sending and receiving packets.
+- Additional restrictions may be imposed on which other machines may be connected to & which channels may be established.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+This initial standard uses version "ics20-1" in the channel handshake.
+
+A future version of this standard could use a different version in the channel handshake,
+and safely alter the packet data format & packet handler semantics.
+
+## Example Implementations
+
+- Implementation of ICS 20 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 20 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Jul 15, 2019 - Draft written
+
+Jul 29, 2019 - Major revisions; cleanup
+
+Aug 25, 2019 - Major revisions, more cleanup
+
+Feb 3, 2020 - Revisions to handle acknowledgements of success & failure
+
+Feb 24, 2020 - Revisions to infer source field, inclusion of version string
+
+July 27, 2020 - Re-addition of source field
+
+Nov 11, 2022 - Addition of a memo field
+
+Sep 22, 2023 - [Support for multi-token packets](https://github.com/cosmos/ibc/pull/1020)
+
+March 5, 2024 - [Support for path forwarding](https://github.com/cosmos/ibc/pull/1090)
+
+June 18, 2024 - [Support for data protobuf encoding](https://github.com/cosmos/ibc/pull/1118)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-failure.png b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-failure.png
new file mode 100644
index 00000000..36eccc1d
Binary files /dev/null and b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-failure.png differ
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-success.png b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-success.png
new file mode 100644
index 00000000..3229fbb3
Binary files /dev/null and b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains-success.png differ
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains.excalidraw b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains.excalidraw
new file mode 100644
index 00000000..01c74839
--- /dev/null
+++ b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/forwarding-3-chains.excalidraw
@@ -0,0 +1,2425 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "arrow",
+ "version": 500,
+ "versionNonce": 547005722,
+ "index": "a6",
+ "isDeleted": false,
+ "id": "kGNyejkGXJAPWBL3iSem9",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 575.5231779834404,
+ "y": 174.3624805827368,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 2.9839978389845783,
+ "height": 1138.761319881039,
+ "seed": 793816151,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391257529,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "byOxN_Be5RoqJHMRG-hDg",
+ "focus": 0.10736976985156056,
+ "gap": 12.149791114464506
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 2.9839978389845783,
+ 1138.761319881039
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1150,
+ "versionNonce": 836846598,
+ "index": "a7",
+ "isDeleted": false,
+ "id": "UiYbIvXxMv8Dp5bJa3fUB",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 928.1594076363192,
+ "y": 409.82489659174723,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 718.7401123046875,
+ "height": 450,
+ "seed": 1216763255,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398151027,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if received tokens trace is prefixed by \"transfer/channelAToB\" {\n transfer tokens from escrow[channelBtoA] to forwarding[channelBtoA]\n} else {\n mint IBC vouchers to forwarding[channelBtoA]\n}\n\nsend packetBtoC{\n sender: forwarding[channelBtoA]\n receiver: chainC...\n}\n\nif sent tokens trace is prefixed by \"transfer/channelBToC\" {\n burn vouchers from forwarding[channelBtoA]\n} else {\n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoC]\n}\n\nstore packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if received tokens trace is prefixed by \"transfer/channelAToB\" {\n transfer tokens from escrow[channelBtoA] to forwarding[channelBtoA]\n} else {\n mint IBC vouchers to forwarding[channelBtoA]\n}\n\nsend packetBtoC{\n sender: forwarding[channelBtoA]\n receiver: chainC...\n}\n\nif sent tokens trace is prefixed by \"transfer/channelBToC\" {\n burn vouchers from forwarding[channelBtoA]\n} else {\n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoC]\n}\n\nstore packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 199,
+ "versionNonce": 522645466,
+ "index": "a9",
+ "isDeleted": false,
+ "id": "byOxN_Be5RoqJHMRG-hDg",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 544.1754309597408,
+ "y": 137.2126894682723,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 70.09996032714844,
+ "height": 25,
+ "seed": 1685913625,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "kGNyejkGXJAPWBL3iSem9",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721390761541,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain A",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain A",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 318,
+ "versionNonce": 2134088154,
+ "index": "aA",
+ "isDeleted": false,
+ "id": "VTjpRVLCJCTyb1Jvl_5HW",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 286.5043327213947,
+ "y": 217.14507867516056,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 263.75982666015625,
+ "height": 175,
+ "seed": 1363487673,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721390783916,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "send packetAtoB {\n sender: chainA...\n receiver: chainC...\n forwardingPath: {\n \"transfer/channelBtoC\"\n }\n}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "send packetAtoB {\n sender: chainA...\n receiver: chainC...\n forwardingPath: {\n \"transfer/channelBtoC\"\n }\n}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 850,
+ "versionNonce": 325918042,
+ "index": "aC",
+ "isDeleted": false,
+ "id": "7yeRy5aZS6ORAypCf5nLj",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1992.292466706081,
+ "y": 879.3893557920742,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 420.399658203125,
+ "height": 175,
+ "seed": 742789945,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398431148,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if source {\n transfer tokens from escrow to chainC...\n} else {\n mint IBC vouchers to chainC...\n}\n\nwrite ack (packetBtoC)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if source {\n transfer tokens from escrow to chainC...\n} else {\n mint IBC vouchers to chainC...\n}\n\nwrite ack (packetBtoC)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 218,
+ "versionNonce": 739724954,
+ "index": "aD",
+ "isDeleted": false,
+ "id": "-LqXV4Ju2E1Ik3GMk8X0k",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 272.18387866690665,
+ "y": 205.41736555811792,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 288.0428630503233,
+ "height": 197.54554923377296,
+ "seed": 1281030455,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "8HGAHjo9alsyXDCxeEhAp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721390783920,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 163,
+ "versionNonce": 749919450,
+ "index": "aE",
+ "isDeleted": false,
+ "id": "Vz7AOU9wIKtBTUtD_1KPd",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 272.18387866690665,
+ "y": 169.96429701350235,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 129.9998779296875,
+ "height": 25,
+ "seed": 1542212183,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721390783931,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "sendTransfer",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "sendTransfer",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 1065,
+ "versionNonce": 698715482,
+ "index": "aF",
+ "isDeleted": false,
+ "id": "2xNUCVThpXTigfqtsGJHc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.0283164259336,
+ "y": 393.53853015581706,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 750.9205799880971,
+ "height": 477.1872215999752,
+ "seed": 643800025,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "N_e9An2TnP5EegdA7TYu0",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391148399,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 404,
+ "versionNonce": 472945562,
+ "index": "aG",
+ "isDeleted": false,
+ "id": "09p1k2GmKEc7_M8tez7hN",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.0283164259336,
+ "y": 360.00196576757037,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 133.99989318847656,
+ "height": 25,
+ "seed": 1468066425,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "8HGAHjo9alsyXDCxeEhAp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391148399,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onRecvPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onRecvPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 813,
+ "versionNonce": 357851674,
+ "index": "aH",
+ "isDeleted": false,
+ "id": "uifUVSpwEQeWvd8_O1w1x",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1989.0668437731438,
+ "y": 825.824601382853,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 133.99989318847656,
+ "height": 25,
+ "seed": 1170029625,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398431148,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onRecvPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onRecvPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 835,
+ "versionNonce": 192220890,
+ "index": "aI",
+ "isDeleted": false,
+ "id": "HYxBGdAxPhxx77rP-2_wP",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1979.7210283852673,
+ "y": 861.7445106177956,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 439.14638420461347,
+ "height": 209.64974254173208,
+ "seed": 1375318265,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "Eqhn1GBxEY7KAgrR903nW",
+ "type": "arrow"
+ },
+ {
+ "id": "N_e9An2TnP5EegdA7TYu0",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721398431148,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 929,
+ "versionNonce": 2133126534,
+ "index": "aJ",
+ "isDeleted": false,
+ "id": "sgKNMd6-7II77xbdG6qOX",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 903.8794591920116,
+ "y": 174.5109319008393,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 0.2013230555763812,
+ "height": 1139.696081087479,
+ "seed": 2100571513,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391263681,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "3YcAjui8eUFaxmbDcKayJ",
+ "focus": 0.2819110820952405,
+ "gap": 12.298242432567008
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.2013230555763812,
+ 1139.696081087479
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 397,
+ "versionNonce": 1137243034,
+ "index": "aL",
+ "isDeleted": false,
+ "id": "3YcAjui8eUFaxmbDcKayJ",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 878.1968563543343,
+ "y": 137.2126894682723,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 71.51995849609375,
+ "height": 25,
+ "seed": 1586837495,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "sgKNMd6-7II77xbdG6qOX",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721390853103,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain B",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain B",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 503,
+ "versionNonce": 1662501574,
+ "index": "aM",
+ "isDeleted": false,
+ "id": "G3-gvgZsqMVqX_oz-iiFH",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1928.7981812626654,
+ "y": 137.2126894682723,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 69.85995483398438,
+ "height": 25,
+ "seed": 725312377,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391270677,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain C",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain C",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1962,
+ "versionNonce": 964280410,
+ "index": "aN",
+ "isDeleted": false,
+ "id": "8HGAHjo9alsyXDCxeEhAp",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 588.0200009252582,
+ "y": 399.47004149999134,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 303.0481639343741,
+ "height": 0.27236065526119546,
+ "seed": 1300355545,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391131179,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "-LqXV4Ju2E1Ik3GMk8X0k",
+ "focus": 0.9649361284756259,
+ "gap": 27.793259208028218
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 303.0481639343741,
+ -0.27236065526119546
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 472,
+ "versionNonce": 1393732358,
+ "index": "aO",
+ "isDeleted": false,
+ "id": "VnIAfK4FcRNB9hjkdBiZO",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.0065641796763031834,
+ "x": 676.0564243364322,
+ "y": 364.80764648224823,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 115.25990295410156,
+ "height": 25,
+ "seed": 1286770201,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391140541,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 3791,
+ "versionNonce": 1980879322,
+ "index": "aP",
+ "isDeleted": false,
+ "id": "N_e9An2TnP5EegdA7TYu0",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1680.9133377421076,
+ "y": 864.3694078026004,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 275.67427632090494,
+ "height": 2.593364124643017,
+ "seed": 1110419127,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721398431148,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "2xNUCVThpXTigfqtsGJHc",
+ "focus": 0.9440017743888038,
+ "gap": 12.9644413280771
+ },
+ "endBinding": {
+ "elementId": "HYxBGdAxPhxx77rP-2_wP",
+ "focus": 0.9104963385077985,
+ "gap": 23.133414322254794
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 275.67427632090494,
+ 2.593364124643017
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 795,
+ "versionNonce": 2003532166,
+ "index": "aQ",
+ "isDeleted": false,
+ "id": "xz1MNvMA9mfmqpo9OKP3G",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.015723577377153575,
+ "x": 1764.7277546688324,
+ "y": 830.3558362693552,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 115.0198974609375,
+ "height": 25,
+ "seed": 1637453049,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391172118,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "packetBtoC",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "packetBtoC",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 1168,
+ "versionNonce": 473142938,
+ "index": "aR",
+ "isDeleted": false,
+ "id": "4WCvYeWRpdBoz-3Q4NnoS",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 929.690800505773,
+ "y": 1088.2019971727436,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 230.83981323242188,
+ "height": 100,
+ "seed": 310816825,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391188999,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "retrieve packetAtoB\nwrite ack (packetAtoB)\n\ndelete packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "retrieve packetAtoB\nwrite ack (packetAtoB)\n\ndelete packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 622,
+ "versionNonce": 1232712538,
+ "index": "aS",
+ "isDeleted": false,
+ "id": "4Io5Rp0F6mIZqiKyi6t0N",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.4555736796583,
+ "y": 1075.3159194011434,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 325.53055656045603,
+ "height": 120.40440728691365,
+ "seed": 161621241,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "P5P9UeoVFMboMNjuA-9AK",
+ "type": "arrow"
+ },
+ {
+ "id": "Eqhn1GBxEY7KAgrR903nW",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391188999,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 798,
+ "versionNonce": 341849690,
+ "index": "aT",
+ "isDeleted": false,
+ "id": "IXOlt8Dp45562Zn5iqois",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.4555736796583,
+ "y": 1042.1592326995321,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 247.27978515625,
+ "height": 25,
+ "seed": 110061111,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391188999,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onAcknowledgementPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onAcknowledgementPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1395,
+ "versionNonce": 1548656730,
+ "index": "aU",
+ "isDeleted": false,
+ "id": "Eqhn1GBxEY7KAgrR903nW",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1955.8554961071927,
+ "y": 1071.3470665084844,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 705.3140050141426,
+ "height": 4.436795705239774,
+ "seed": 827374745,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721398431148,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "HYxBGdAxPhxx77rP-2_wP",
+ "focus": -0.9721401838478735,
+ "gap": 23.865532278074397
+ },
+ "endBinding": {
+ "elementId": "4Io5Rp0F6mIZqiKyi6t0N",
+ "focus": -0.958135027750018,
+ "gap": 7.555360852935905
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -705.3140050141426,
+ 4.436795705239774
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1075,
+ "versionNonce": 946209926,
+ "index": "aV",
+ "isDeleted": false,
+ "id": "yw0PWwV3nWEk41QKiIhyO",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 6.280976262094214,
+ "x": 1502.8577314573058,
+ "y": 1036.5200686073217,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 173.19985961914062,
+ "height": 25,
+ "seed": 1570375673,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391303480,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ack (packetBtoC)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ack (packetBtoC)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1239,
+ "versionNonce": 357107014,
+ "index": "aW",
+ "isDeleted": false,
+ "id": "P5P9UeoVFMboMNjuA-9AK",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 897.5759110554653,
+ "y": 1197.6542553507638,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 310.35420021721086,
+ "height": 3.0558842002553774,
+ "seed": 1318195577,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391321341,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "4Io5Rp0F6mIZqiKyi6t0N",
+ "focus": -0.9762618588252879,
+ "gap": 19.87966262419286
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -310.35420021721086,
+ 3.0558842002553774
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1099,
+ "versionNonce": 597104986,
+ "index": "aX",
+ "isDeleted": false,
+ "id": "FkSCmHbUSF3emeF79Q69J",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 6.2797425472774595,
+ "x": 655.1772450696166,
+ "y": 1163.2352370104911,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 173.4398651123047,
+ "height": 25,
+ "seed": 1917942457,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391251401,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ack (packetAtoB)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ack (packetAtoB)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 461,
+ "versionNonce": 1181123930,
+ "index": "aZ",
+ "isDeleted": false,
+ "id": "tED5edHiPU164udhrVD29",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.0283164259336,
+ "y": 879.3899003587428,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 147.49989318847656,
+ "height": 25,
+ "seed": 1841434455,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391475066,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "no ack written!",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "no ack written!",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 1183,
+ "versionNonce": 1221734042,
+ "index": "aa",
+ "isDeleted": false,
+ "id": "pSRus7Synn9o0xhodzzx0",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 319.89830364187833,
+ "y": 1217.053116418913,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 51.359954833984375,
+ "height": 25,
+ "seed": 885022647,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391233370,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "no op",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "no op",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 641,
+ "versionNonce": 377834330,
+ "index": "ab",
+ "isDeleted": false,
+ "id": "jHYS_rN2WicEWEsWs4A38",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 307.6630768157636,
+ "y": 1204.1670386473127,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 260.88656550552963,
+ "height": 46.78595318915578,
+ "seed": 1663652055,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [],
+ "updated": 1721391233370,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 827,
+ "versionNonce": 557240346,
+ "index": "ac",
+ "isDeleted": false,
+ "id": "PT6crO9bjUcpODS5dcA3p",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 307.6630768157636,
+ "y": 1171.0103519457014,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 247.27978515625,
+ "height": 25,
+ "seed": 1972837879,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391233370,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onAcknowledgementPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onAcknowledgementPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1052,
+ "versionNonce": 51421146,
+ "index": "b0b",
+ "isDeleted": false,
+ "id": "ZN8foweFftsQiJ9v6GG-5",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1967.88026675804,
+ "y": 175.54838993432872,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 0.2013230555763812,
+ "height": 1139.696081087479,
+ "seed": 44717594,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391293069,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.2013230555763812,
+ 1139.696081087479
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 845,
+ "versionNonce": 522914650,
+ "index": "b0c",
+ "isDeleted": false,
+ "id": "otleiEczPEeoFdhofVq2_",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 575.6986717813893,
+ "y": 1453.99165110909,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 7.141516764452717,
+ "height": 1325.2279701053567,
+ "seed": 191803206,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391605189,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "RM7zJhNaCicQ-RxH90Hx-",
+ "focus": 0.10920725118350195,
+ "gap": 12.149791114464506
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 7.141516764452717,
+ 1325.2279701053567
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1375,
+ "versionNonce": 764063046,
+ "index": "b0d",
+ "isDeleted": false,
+ "id": "rQtffJqtXGbFhpthMOo-2",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 928.3349014342682,
+ "y": 1688.933917709401,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 718.7401123046875,
+ "height": 450,
+ "seed": 564450950,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398222119,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if received tokens trace is prefixed by \"transfer/channelAToB\" {\n transfer tokens from escrow[channelBtoA] to forwarding[channelBtoA]\n} else {\n mint IBC vouchers to forwarding[channelBtoA]\n}\n\nsend packetBtoC{\n sender: forwarding[channelBtoA]\n receiver: chainC...\n}\n\nif sent tokens trace is prefixed by \"transfer/channelBToC\" {\n burn vouchers from forwarding[channelBtoA]\n} else {\n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoC]\n}\n\nstore packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if received tokens trace is prefixed by \"transfer/channelAToB\" {\n transfer tokens from escrow[channelBtoA] to forwarding[channelBtoA]\n} else {\n mint IBC vouchers to forwarding[channelBtoA]\n}\n\nsend packetBtoC{\n sender: forwarding[channelBtoA]\n receiver: chainC...\n}\n\nif sent tokens trace is prefixed by \"transfer/channelBToC\" {\n burn vouchers from forwarding[channelBtoA]\n} else {\n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoC]\n}\n\nstore packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 295,
+ "versionNonce": 1494092294,
+ "index": "b0e",
+ "isDeleted": false,
+ "id": "RM7zJhNaCicQ-RxH90Hx-",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 544.3509247576897,
+ "y": 1416.8418599946256,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 70.09996032714844,
+ "height": 25,
+ "seed": 890508742,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "otleiEczPEeoFdhofVq2_",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain A",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain A",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 414,
+ "versionNonce": 1697366150,
+ "index": "b0f",
+ "isDeleted": false,
+ "id": "zAkLXu88_FnWJxIxHVi0W",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 286.6798265193436,
+ "y": 1496.7742492015136,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 263.75982666015625,
+ "height": 175,
+ "seed": 1859742982,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "send packetAtoB {\n sender: chainA...\n receiver: chainC...\n forwardingPath: {\n \"transfer/channelBtoC\"\n }\n}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "send packetAtoB {\n sender: chainA...\n receiver: chainC...\n forwardingPath: {\n \"transfer/channelBtoC\"\n }\n}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 314,
+ "versionNonce": 959305478,
+ "index": "b0h",
+ "isDeleted": false,
+ "id": "W0brwFWajXGedkh5jSqAh",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 272.3593724648556,
+ "y": 1485.046536084471,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 288.0428630503233,
+ "height": 197.54554923377296,
+ "seed": 330982278,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "EYCU59bcGM0yY9_7zS99M",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 259,
+ "versionNonce": 796675462,
+ "index": "b0i",
+ "isDeleted": false,
+ "id": "9YKuQ7YLqu5mQYcg2OyWh",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 272.3593724648556,
+ "y": 1449.5934675398557,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 129.9998779296875,
+ "height": 25,
+ "seed": 1190134470,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "sendTransfer",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "sendTransfer",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 1161,
+ "versionNonce": 652520646,
+ "index": "b0j",
+ "isDeleted": false,
+ "id": "ZYWW3maO3Y4gM6KO-JusL",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.2038102238826,
+ "y": 1673.16770068217,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 750.9205799880971,
+ "height": 477.1872215999752,
+ "seed": 414169606,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "3Md7voHeufaQNWuPkH2v7",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 500,
+ "versionNonce": 1447605062,
+ "index": "b0k",
+ "isDeleted": false,
+ "id": "6b6gc4l3ETpzflmkGMlAM",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.2038102238826,
+ "y": 1639.6311362939236,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 133.99989318847656,
+ "height": 25,
+ "seed": 1609992518,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "EYCU59bcGM0yY9_7zS99M",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onRecvPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onRecvPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 917,
+ "versionNonce": 1400979846,
+ "index": "b0l",
+ "isDeleted": false,
+ "id": "Fyh3IF7aoHrMEG2TKd1KF",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1981.4535784228806,
+ "y": 2108.947302945244,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 133.99989318847656,
+ "height": 25,
+ "seed": 203518086,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398440253,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onRecvPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onRecvPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1249,
+ "versionNonce": 51497754,
+ "index": "b0n",
+ "isDeleted": false,
+ "id": "0_WQp1XiXnnEWPvakU95Q",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 904.0549529899606,
+ "y": 1454.1401024271927,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 2.2800825183104507,
+ "height": 1336.6693473871323,
+ "seed": 41387782,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391611013,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "L-BH2oycGExYPTPNjcQkp",
+ "focus": 0.28282026169540886,
+ "gap": 12.298242432567122
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 2.2800825183104507,
+ 1336.6693473871323
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 493,
+ "versionNonce": 1104275334,
+ "index": "b0o",
+ "isDeleted": false,
+ "id": "L-BH2oycGExYPTPNjcQkp",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 878.3723501522832,
+ "y": 1416.8418599946256,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 71.51995849609375,
+ "height": 25,
+ "seed": 960280134,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "0_WQp1XiXnnEWPvakU95Q",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain B",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain B",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 600,
+ "versionNonce": 1948439450,
+ "index": "b0p",
+ "isDeleted": false,
+ "id": "V0em9RQid1YsshTW2NoU1",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1928.9736750606144,
+ "y": 1416.8418599946256,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 69.85995483398438,
+ "height": 25,
+ "seed": 370802054,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [
+ {
+ "id": "ibBezEs6ZMBff_zQs5kY4",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391615405,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "chain C",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "chain C",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 2184,
+ "versionNonce": 1891179738,
+ "index": "b0q",
+ "isDeleted": false,
+ "id": "EYCU59bcGM0yY9_7zS99M",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 588.195494723207,
+ "y": 1679.0992120263445,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 303.0481639343741,
+ "height": 0.27236065526119546,
+ "seed": 1313627334,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391387172,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "W0brwFWajXGedkh5jSqAh",
+ "focus": 0.9649361284756256,
+ "gap": 27.793259208028076
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 303.0481639343741,
+ -0.27236065526119546
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 568,
+ "versionNonce": 5698694,
+ "index": "b0r",
+ "isDeleted": false,
+ "id": "hcnv2P1VMccbZXga3Ihtc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.0065641796763031834,
+ "x": 676.2319181343812,
+ "y": 1644.4368170086013,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 115.25990295410156,
+ "height": 25,
+ "seed": 1735447558,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 3998,
+ "versionNonce": 2012508570,
+ "index": "b0s",
+ "isDeleted": false,
+ "id": "3Md7voHeufaQNWuPkH2v7",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1681.0888315400566,
+ "y": 2144.00504226104,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 272.67427632090494,
+ "height": 2.5734363123119692,
+ "seed": 730096454,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391387172,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "ZYWW3maO3Y4gM6KO-JusL",
+ "focus": 0.9440017743888054,
+ "gap": 12.9644413280771
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 272.67427632090494,
+ 2.5734363123119692
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 891,
+ "versionNonce": 1109134086,
+ "index": "b0t",
+ "isDeleted": false,
+ "id": "oTAZSfgKxlBGR45JEtCBh",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.015723577377153575,
+ "x": 1764.9032484667814,
+ "y": 2109.9850067957086,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 115.0198974609375,
+ "height": 25,
+ "seed": 484706950,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391386965,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "packetBtoC",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "packetBtoC",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 996,
+ "versionNonce": 494064922,
+ "index": "b0w",
+ "isDeleted": false,
+ "id": "MpYiaWdvAYvOo4n2pCRuA",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 917.6310674776073,
+ "y": 2219.7884032258853,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 247.27978515625,
+ "height": 25,
+ "seed": 1779273798,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391458354,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onAcknowledgementPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onAcknowledgementPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 2057,
+ "versionNonce": 130765018,
+ "index": "b0x",
+ "isDeleted": false,
+ "id": "lrbU8XgF0ilv3bAe90yZH",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1961.0907322739886,
+ "y": 2254.1539085562645,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 286.9462207254535,
+ "height": 2.6969505102097173,
+ "seed": 1807718278,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721398452038,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "KWbGknRtIF4Je9g27kDgk",
+ "focus": -1.0648555439596428,
+ "gap": 19.0338254582
+ },
+ "endBinding": {
+ "elementId": "1yRqY9xPUXVqt9V-VV6c2",
+ "focus": -0.9299803073935413,
+ "gap": 7.3891439850493725
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -286.9462207254535,
+ 2.6969505102097173
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1313,
+ "versionNonce": 998632794,
+ "index": "b0y",
+ "isDeleted": false,
+ "id": "vb_o7HkT18BthRdJJo3m-",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 6.280976262094214,
+ "x": 1740.860003527854,
+ "y": 2221.5047065029307,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 173.19985961914062,
+ "height": 25,
+ "seed": 1345238726,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391535282,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ack (packetBtoC)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ack (packetBtoC)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1757,
+ "versionNonce": 1153007494,
+ "index": "b0z",
+ "isDeleted": false,
+ "id": "XrQ-OzIJlfW0AtYf-n4CW",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 895.4123418568441,
+ "y": 2675.7817580137844,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 304.33740353601297,
+ "height": 0.07045303591075935,
+ "seed": 1878519302,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391571178,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "1yRqY9xPUXVqt9V-VV6c2",
+ "focus": -1.0245053530289139,
+ "gap": 24.512758391025955
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -304.33740353601297,
+ 0.07045303591075935
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1259,
+ "versionNonce": 1750693638,
+ "index": "b10",
+ "isDeleted": false,
+ "id": "4pScE4wr7Uz7tGiy6plMI",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 6.2797425472774595,
+ "x": 663.948625390083,
+ "y": 2636.2792980822765,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 173.4398651123047,
+ "height": 25,
+ "seed": 135806278,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391599395,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ack (packetAtoB)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ack (packetAtoB)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 570,
+ "versionNonce": 1384718790,
+ "index": "b11",
+ "isDeleted": false,
+ "id": "dtGr8tLZXr3N5YqYxCrP1",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 918.3683205692284,
+ "y": 2160.6900501944046,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 147.49989318847656,
+ "height": 25,
+ "seed": 770483334,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391465614,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "no ack written!",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "no ack written!",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 1361,
+ "versionNonce": 1364363974,
+ "index": "b12",
+ "isDeleted": false,
+ "id": "74D0-xiw8ZjJDGGjoUxal",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 322.52561989624587,
+ "y": 2686.6985273177033,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 61.340003967285156,
+ "height": 25,
+ "seed": 1127359430,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391589389,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "refund",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "refund",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 812,
+ "versionNonce": 144280134,
+ "index": "b13",
+ "isDeleted": false,
+ "id": "PTZBBJTzK8a2D886-p0CT",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 310.29039307013113,
+ "y": 2673.812449546103,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 260.88656550552963,
+ "height": 46.78595318915578,
+ "seed": 1778760454,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [],
+ "updated": 1721391577775,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 998,
+ "versionNonce": 180113798,
+ "index": "b14",
+ "isDeleted": false,
+ "id": "j8Atvv2w0yuXiYExyaV67",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 310.29039307013113,
+ "y": 2640.6557628444916,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 247.27978515625,
+ "height": 25,
+ "seed": 2028235334,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721391577775,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "onAcknowledgementPacket",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "onAcknowledgementPacket",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 1199,
+ "versionNonce": 1535885018,
+ "index": "b15",
+ "isDeleted": false,
+ "id": "ibBezEs6ZMBff_zQs5kY4",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1968.055760555989,
+ "y": 1455.177560460682,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 1.9992338467293393,
+ "height": 1324.8808925762319,
+ "seed": 1856713094,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721391615405,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "V0em9RQid1YsshTW2NoU1",
+ "focus": -0.11768980748794368,
+ "gap": 13.335700466056323
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 1.9992338467293393,
+ 1324.8808925762319
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1846,
+ "versionNonce": 1734341863,
+ "index": "b17",
+ "isDeleted": false,
+ "id": "rYcgj44Q9ycg_sbKPGb9i",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 933.134989975332,
+ "y": 2259.7976515360433,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 718.739990234375,
+ "height": 400,
+ "seed": 251475782,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721595832888,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if sent tokens trace is prefixed by \"transfer/channelBToC\" {\n mint IBC vouchers to forwarding[channelBtoA]\n} else {\n transfer tokens from escrow[channelBtoC] to forwarding[channelBtoA]\n}\n\nretrieve packetAtoB\nif sent tokens trace is prefixed by \"transfer/channelBToA\" {\n burn IBC vouchers from forwarding[channelBtoA]\n} else { \n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoA]\n}\n\nwrite error ack (packetAtoB)\n\ndelete packetAtoB",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if sent tokens trace is prefixed by \"transfer/channelBToC\" {\n mint IBC vouchers to forwarding[channelBtoA]\n} else {\n transfer tokens from escrow[channelBtoC] to forwarding[channelBtoA]\n}\n\nretrieve packetAtoB\nif sent tokens trace is prefixed by \"transfer/channelBToA\" {\n burn IBC vouchers from forwarding[channelBtoA]\n} else { \n transfer tokens from forwarding[channelBtoA] to escrow[channelBtoA]\n}\n\nwrite error ack (packetAtoB)\n\ndelete packetAtoB",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 1269,
+ "versionNonce": 159900570,
+ "index": "b18",
+ "isDeleted": false,
+ "id": "1yRqY9xPUXVqt9V-VV6c2",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 919.9251002478702,
+ "y": 2248.937245537475,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 746.8302673156155,
+ "height": 421.4993359234997,
+ "seed": 1109548678,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "lrbU8XgF0ilv3bAe90yZH",
+ "type": "arrow"
+ },
+ {
+ "id": "XrQ-OzIJlfW0AtYf-n4CW",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721391558425,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 970,
+ "versionNonce": 1714102470,
+ "index": "b1A",
+ "isDeleted": false,
+ "id": "sGvqLevJTSKlQJ2bLFNVN",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1994.3967674333971,
+ "y": 2157.0275201708705,
+ "strokeColor": "#e03131",
+ "backgroundColor": "transparent",
+ "width": 288.41973876953125,
+ "height": 75,
+ "seed": 1648319386,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721398440253,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ERROR!\n\nwrite error ack (packetBtoC)",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ERROR!\n\nwrite error ack (packetBtoC)",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 932,
+ "versionNonce": 517412870,
+ "index": "b1C",
+ "isDeleted": false,
+ "id": "KWbGknRtIF4Je9g27kDgk",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1980.1245577321888,
+ "y": 2141.26030290846,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 320.7643745091394,
+ "height": 106.15962645950245,
+ "seed": 2029829402,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "lrbU8XgF0ilv3bAe90yZH",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721398440253,
+ "link": null,
+ "locked": false
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/source-and-sink-zones.png b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/source-and-sink-zones.png
new file mode 100644
index 00000000..1e77b983
Binary files /dev/null and b/ibc/next/spec/app/ics-020-fungible-token-transfer/deprecated/source-and-sink-zones.png differ
diff --git a/ibc/next/spec/app/ics-027-interchain-accounts/README.mdx b/ibc/next/spec/app/ics-027-interchain-accounts/README.mdx
new file mode 100644
index 00000000..e81632ff
--- /dev/null
+++ b/ibc/next/spec/app/ics-027-interchain-accounts/README.mdx
@@ -0,0 +1,826 @@
+---
+ics: 27
+title: Interchain Accounts
+stage: draft
+category: IBC/APP
+requires: [25, 26]
+kind: instantiation
+version_compatibility: null
+authors:
+ - name: Tony Yun
+ email: tony@chainapsis.com
+ - name: Dogemos
+ email: josh@tendermint.com
+ - name: Sean King
+ email: sean@interchain.io
+created: 2019-08-01
+modified: 2020-07-14
+---
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for the account management system over an IBC channel between separate chains.
+
+### Motivation
+
+ICS-27 Interchain Accounts outlines a cross-chain account management protocol built upon IBC. ICS-27 enabled chains can programmatically create accounts on other ICS-27 enabled chains & control these accounts via IBC transactions (instead of signing with a private key). Interchain accounts retain all of the capabilities of a normal account (i.e. stake, send, vote) but instead are managed by a separate chain via IBC in a way such that the owner account on the controller chain retains full control over any interchain account(s) it registers on host chain(s).
+
+### Definitions
+
+- `Host Chain`: The chain where the interchain account is registered. The host chain listens for IBC packets from a controller chain which contain instructions (e.g. cosmos SDK messages) that the interchain account will execute.
+- `Controller Chain`: The chain registering and controlling an account on a host chain. The controller chain sends IBC packets to the host chain to control the account.
+- `Interchain Account`: An account on a host chain. An interchain account has all the capabilities of a normal account. However, rather than signing transactions with a private key, a controller chain will send IBC packets to the host chain which signals what transactions the interchain account must execute.
+- `Interchain Account Owner`: An account on the controller chain. Every interchain account on a host chain has a respective owner account on the controller chain.
+
+The IBC handler interface & IBC relayer module interface are as defined in [ICS-25](../../core/ics-025-handler-interface) and [ICS-26](../../core/ics-026-routing-module), respectively.
+
+### Desired properties
+
+- Permissionless: An interchain account may be created by any actor without the approval of a third party (e.g. chain governance). Note: Individual implementations may implement their own permissioning scheme, however the protocol must not require permissioning from a trusted party to be secure.
+- Fault isolation: A controller chain must not be able to control accounts registered by other controller chains. For example, in the case of a fork attack on a controller chain, only the interchain accounts registered by the forked chain will be vulnerable.
+- The ordering of transactions sent to an interchain account on a host chain must be maintained. Transactions must be executed by an interchain account in the order in which they are sent by the controller chain.
+- If a channel closes, the controller chain must be able to regain access to registered interchain accounts by simply opening a new channel.
+- Each interchain account is owned by a single account on the controller chain. Only the owner account on the controller chain is authorized to control the interchain account. The controller chain is responsible for enforcing this logic.
+- The controller chain must store the account address of any owned interchain accounts registered on host chains.
+- A host chain must have the ability to limit interchain account functionality on its chain as necessary (e.g. a host chain can decide that interchain accounts registered on the host chain cannot take part in staking).
+
+## Technical specification
+
+### General design
+
+A chain can utilize one or both parts of the interchain accounts protocol (*controlling* and *hosting*). A controller chain that registers accounts on other host chains (that support interchain accounts) does not necessarily have to allow other controller chains to register accounts on its chain, and vice versa.
+
+This specification defines the general way to register an interchain account and send tx bytes to be executed on behalf of the owner account. The host chain is responsible for deserializing and executing the tx bytes and the controller chain must know how the host chain will handle the tx bytes in advance of sending a packet, thus this must be negotiated during channel creation.
+
+### Controller chain contract
+
+#### **RegisterInterchainAccount**
+
+`RegisterInterchainAccount` is the entry point to registering an interchain account.
+It generates a new controller portID using the owner account address.
+It will bind to the controller portID and
+call 04-channel `ChanOpenInit`. An error is returned if the controller portID is already in use.
+A `ChannelOpenInit` event is emitted which can be picked up by an offchain process such as a relayer.
+The account will be registered during the `OnChanOpenTry` step on the host chain.
+This function must be called after an `OPEN` connection is already established with the given connection identifier.
+The caller must provide the complete channel version. This MUST include the ICA version with complete metadata and it MAY include
+versions of other middleware that is wrapping ICA on both sides of the channel. Note this will require contextual information
+on what middleware is enabled on either end of the channel. Thus it is recommended that an ICA-auth application construct the ICA
+version automatically and allow for users to optionally enable additional middleware versioning.
+
+```typescript
+function RegisterInterchainAccount(connectionId: Identifier, owner: string, version: string) returns (error) {
+}
+```
+
+#### **SendTx**
+
+`SendTx` is used to send an IBC packet containing instructions (messages) to an interchain account on a host chain for a given interchain account owner.
+
+```typescript
+function SendTx(
+ capability: CapabilityKey,
+ connectionId: Identifier,
+ portId: Identifier,
+ icaPacketData: InterchainAccountPacketData,
+ timeoutTimestamp uint64
+): uint64 {
+ // check if there is a currently active channel for
+ // this portId and connectionId, which also implies an
+ // interchain account has been registered using
+ // this portId and connectionId
+ activeChannelID, found = GetActiveChannelID(portId, connectionId)
+ abortTransactionUnless(found)
+
+ // validate timeoutTimestamp
+ abortTransactionUnless(timeoutTimestamp <= currentTimestamp())
+
+ // validate icaPacketData
+ abortTransactionUnless(icaPacketData.type == EXECUTE_TX)
+ abortTransactionUnless(icaPacketData.data != nil)
+
+ // send icaPacketData to the host chain on the active channel
+ sequence = handler.sendPacket(
+ capability,
+ portId, // source port ID
+ activeChannelID, // source channel ID
+ 0,
+ timeoutTimestamp,
+ protobuf.marshal(icaPacketData) // protobuf-marshalled bytes of packet data
+ )
+
+ return sequence
+}
+```
+
+### Host chain contract
+
+#### **RegisterInterchainAccount**
+
+`RegisterInterchainAccount` is called on the `OnChanOpenTry` step during the channel creation handshake.
+
+```typescript
+function RegisterInterchainAccount(counterpartyPortId: Identifier, connectionID: Identifier) returns (nil) {
+ // checks to make sure the account has not already been registered
+ // creates a new address on chain deterministically given counterpartyPortId and underlying connectionID
+ // calls SetInterchainAccountAddress()
+}
+```
+
+#### **AuthenticateTx**
+
+`AuthenticateTx` is called before `ExecuteTx`.
+`AuthenticateTx` checks that the signer of a particular message is the interchain account associated with the counterparty portID of the channel that the IBC packet was sent on.
+
+```typescript
+function AuthenticateTx(msgs []Any, connectionId string, portId string) returns (error) {
+ // GetInterchainAccountAddress(portId, connectionId)
+ // if interchainAccountAddress != msgSigner return error
+}
+```
+
+#### **ExecuteTx**
+
+Executes each message sent by the owner account on the controller chain.
+
+```typescript
+function ExecuteTx(sourcePort: Identifier, channel Channel, msgs []Any) returns (resultString, error) {
+ // validate each message
+ // retrieve the interchain account for the given channel by passing in source port and channel's connectionID
+ // verify that interchain account is authorized signer of each message
+ // execute each message
+ // return result of transaction
+}
+```
+
+### Utility functions
+
+```typescript
+// Sets the active channel for a given portID and connectionID.
+function SetActiveChannelID(portId: Identifier, connectionId: Identifier, channelId: Identifier) returns (error){
+}
+
+// Returns the ID of the active channel for a given portID and connectionID, if present.
+function GetActiveChannelID(portId: Identifier, connectionId: Identifier) returns (Identifier, boolean){
+}
+
+// Stores the address of the interchain account in state.
+function SetInterchainAccountAddress(portId: Identifier, connectionId: Identifier, address: string) returns (string) {
+}
+
+// Retrieves the interchain account from state.
+function GetInterchainAccountAddress(portId: Identifier, connectionId: Identifier) returns (string, bool){
+}
+```
+
+### Register & controlling flows
+
+#### Register account flow
+
+To register an interchain account we require an off-chain process (relayer) to listen for `ChannelOpenInit` events with the capability to finish a channel creation handshake on a given connection.
+
+1. The controller chain binds a new IBC port with the controller portID for a given *interchain account owner address*.
+
+This port will be used to create channels between the controller & host chain for a specific owner/interchain account pair. Only the account with `{owner-account-address}` matching the bound port will be authorized to send IBC packets over channels created with the controller portID. It is up to each controller chain to enforce this port registration and access on the controller side.
+
+2. The controller chain emits an event signaling to open a new channel on this port given a connection.
+3. A relayer listening for `ChannelOpenInit` events will continue the channel creation handshake.
+4. During the `OnChanOpenTry` callback on the host chain an interchain account will be registered and a mapping of the interchain account address to the owner account address will be stored in state (this is used for authenticating transactions on the host chain at execution time).
+5. During the `OnChanOpenAck` callback on the controller chain a record of the interchain account address registered on the host chain during `OnChanOpenTry` is set in state with a mapping from (controller portID, controller connectionID) -> interchain account address. See [metadata negotiation](#metadata-negotiation) section below for how to implement this.
+6. During the `OnChanOpenAck` & `OnChanOpenConfirm` callbacks on the controller & host chains respectively, the [active-channel](#active-channels) for this interchain account/owner pair, is set in state.
+
+#### Active channels
+
+The controller and host chain must keep track of an `active-channel` for each registered interchain account. The `active-channel` is set during the channel creation handshake process. This is a safety mechanism that allows a controller chain to regain access to an interchain account on a host chain in case of a channel closing.
+
+An example of an active channel on the controller chain can look like this:
+
+```typescript
+{
+ // Controller Chain
+ SourcePortId: `icacontroller-`,
+ SourceChannelId: ``,
+ // Host Chain
+ CounterpartyPortId: `icahost`,
+ CounterpartyChannelId: ``,
+}
+```
+
+In the event of a channel closing, the active channel may be replaced by starting a new channel handshake with the same port identifiers on the same underlying connection of the original active channel. ICS-27 channels can only be closed in the event of a timeout (if the implementation uses ordered channels) or in the unlikely event of a light client attack. Controller chains must retain the ability to open new ICS-27 channels and reset the active channel for a particular portID (containing `{owner-account-address}`) and connectionID pair.
+
+The controller and host chains must verify that any new channel maintains the same metadata as the previous active channel to ensure that the parameters of the interchain account remain the same even after replacing the active channel. The `Address` of the metadata should not be verified since it is expected to be empty at the INIT stage, and the host chain will regenerate the exact same address on TRY, because it is expected to generate the interchain account address deterministically from the controller portID and connectionID (both of which must remain the same).
+
+#### **Metadata negotiation**
+
+ICS-27 takes advantage of [ICS-04 channel version negotiation](/ibc/next/spec/core/ics-004-channel-and-packet-semantics/README) to negotiate metadata and channel parameters during the channel handshake. The metadata will contain the encoding format along with the transaction type so that the counterparties can agree on the structure and encoding of the interchain transactions. The metadata sent from the host chain on the TRY step will also contain the interchain account address, so that it can be relayed to the controller chain. At the end of the channel handshake, both the controller and host chains will store a mapping of (controller chain portID, controller/host connectionID) to the newly registered interchain account address ([account registration flow](#register-account-flow)).
+
+ICS-04 allows for each channel version negotiation to be application-specific. In the case of interchain accounts, the channel version will be a string of a JSON struct containing all the relevant metadata intended to be relayed to the counterparty during the channel handshake step ([see summary below](#metadata-negotiation-summary)).
+
+Combined with the one channel per interchain account approach, this method of metadata negotiation allows us to pass the address of the interchain account back to the controller chain and create a mapping from (controller portID, controller connection ID) -> interchain account address during the `OnChanOpenAck` callback. As outlined in the [controlling flow](#controlling-flow), a controller chain will need to know the address of a registered interchain account in order to send transactions to the account on the host chain.
+
+#### **Metadata negotiation summary**
+
+`interchain-account-address` is the address of the interchain account registered on the host chain by the controller chain.
+
+- **INIT**
+
+Initiator: Controller
+
+Datagram: ChanOpenInit
+
+Chain Acted Upon: Controller
+
+Version:
+
+```json
+{
+ "Version": "ics27-1",
+ "ControllerConnectionId": "self_connection_id",
+ "HostConnectionId": "counterparty_connection_id",
+ "Address": "",
+ "Encoding": "requested_encoding_type",
+ "TxType": "requested_tx_type",
+}
+```
+
+Comments: The address is left empty since this will be generated and relayed back by the host chain. The connection identifiers must be included to ensure that if a new channel needs to be opened (in case active channel times out), then we can ensure that the new channel is opened on the same connection. This will ensure that the interchain account is always connected to the same counterparty chain.
+
+- **TRY**
+
+Initiator: Relayer
+
+Datagram: ChanOpenTry
+
+Chain Acted Upon: Host
+
+Version:
+
+```json
+{
+ "Version": "ics27-1",
+ "ControllerConnectionId": "counterparty_connection_id",
+ "HostConnectionId": "self_connection_id",
+ "Address": "interchain_account_address",
+ "Encoding": "negotiated_encoding_type",
+ "TxType": "negotiated_tx_type",
+}
+```
+
+Comments: The ICS-27 application on the host chain is responsible for returning this version given the counterparty version set by the controller chain in INIT. The host chain must agree with the single encoding type and a single tx type that is requested by the controller chain (ie. included in counterparty version). If the requested encoding or tx type is not supported, then the host chain must return an error and abort the handshake.
+The host chain must also generate the interchain account address and populate the address field in the version with the interchain account address string.
+
+- **ACK**
+
+Initiator: Relayer
+
+Datagram: ChanOpenAck
+
+Chain Acted Upon: Controller
+
+CounterpartyVersion:
+
+```json
+{
+ "Version": "ics27-1",
+ "ControllerConnectionId": "self_connection_id",
+ "HostConnectionId": "counterparty_connection_id",
+ "Address": "interchain_account_address",
+ "Encoding": "negotiated_encoding_type",
+ "TxType": "negotiated_tx_type",
+}
+```
+
+Comments: On the ChanOpenAck step, the ICS27 application on the controller chain must verify the version string chosen by the host chain on ChanOpenTry. The controller chain must verify that it can support the negotiated encoding and tx type selected by the host chain. If either is unsupported, then it must return an error and abort the handshake.
+If both are supported, then the controller chain must store a mapping from the channel's portID to the provided interchain account address and return successfully.
+
+#### Controlling flow
+
+Once an interchain account is registered on the host chain a controller chain can begin sending instructions (messages) to the host chain to control the account.
+
+1. The controller chain calls `SendTx` and passes message(s) that will be executed on the host side by the associated interchain account (determined by the controller side port identifier)
+
+Cosmos SDK pseudo-code example:
+
+```golang
+// connectionId is the identifier for the controller connection
+interchainAccountAddress := GetInterchainAccountAddress(portId, connectionId)
+msg := &banktypes.MsgSend{FromAddress: interchainAccountAddress, ToAddress: ToAddress, Amount: amount}
+icaPacketData = InterchainAccountPacketData{
+ Type: types.EXECUTE_TX,
+ Data: serialize(msg),
+ Memo: "memo",
+}
+
+// Sends the message to the host chain, where it will eventually be executed
+SendTx(ownerAddress, connectionId, portID, data, timeout)
+```
+
+2. The host chain upon receiving the IBC packet will call `DeserializeTx`.
+
+3. The host chain will then call `AuthenticateTx` and `ExecuteTx` for each message and return an acknowledgment containing a success or error.
+
+Messages are authenticated on the host chain by taking the controller side port identifier and calling `GetInterchainAccountAddress(controllerPortId, hostConnectionId)` to get the expected interchain account address for the current controller port and connection identifier. If the signer of this message does not match the expected account address then authentication will fail.
+
+### Packet Data
+
+`InterchainAccountPacketData` contains an array of messages that an interchain account can execute and a memo string that is sent to the host chain as well as the packet `type`. ICS-27 version 1 has only one type `EXECUTE_TX`.
+
+```proto
+message InterchainAccountPacketData {
+ enum type
+ bytes data = 1;
+ string memo = 2;
+}
+```
+
+The acknowledgment packet structure is defined as in [ics4](https://github.com/cosmos/ibc-go/blob/main/proto/ibc/core/channel/v1/channel.proto#L135-L148). If an error occurs on the host chain the acknowledgment contains the error message.
+
+```proto
+message Acknowledgement {
+ // response contains either a result or an error and must be non-empty
+ oneof response {
+ bytes result = 21;
+ string error = 22;
+ }
+}
+```
+
+### Custom logic
+
+ICS-27 relies on [ICS-30 middleware architecture](../ics-030-middleware) to provide the option for application developers to apply custom logic on the success or fail of ICS-27 packets.
+
+Controller chains will wrap `OnAcknowledgementPacket` & `OnTimeoutPacket` to handle the success or fail cases for ICS-27 packets.
+
+### Port & channel setup
+
+The interchain account module on a host chain must always bind to a port with the id `icahost`. Controller chains will bind to ports dynamically, as specified in the identifier format [section](#identifier-formats).
+
+The example below assumes a module is implementing the entire `InterchainAccountModule` interface. The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialized) to bind to the appropriate port.
+
+```typescript
+function setup() {
+ capability = routingModule.bindPort("icahost", ModuleCallbacks{
+ onChanOpenInit,
+ onChanOpenTry,
+ onChanOpenAck,
+ onChanOpenConfirm,
+ onChanCloseInit,
+ onChanCloseConfirm,
+ onChanUpgradeInit, // read-only
+ onChanUpgradeTry, // read-only
+ onChanUpgradeAck, // read-only
+ onChanUpgradeOpen,
+ onRecvPacket,
+ onTimeoutPacket,
+ onAcknowledgePacket,
+ onTimeoutPacketClose
+ })
+ claimCapability("port", capability)
+}
+```
+
+Once the `setup` function has been called, channels can be created via the IBC routing module.
+
+### Channel lifecycle management
+
+An interchain account module will accept new channels from any module on another machine, if and only if the channel initialization step is being invoked from the controller chain.
+
+```typescript
+// Called on Controller Chain by InitInterchainAccount
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string
+): (version: string, err: Error) {
+ // validate port format
+ abortTransactionUnless(validateControllerPortParams(portIdentifier))
+ // only allow channels to be created on the "icahost" port on the counterparty chain
+ abortTransactionUnless(counterpartyPortIdentifier === "icahost")
+
+ // retrieve channel and connection to access connection ID and counterparty connection ID
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ connectionId = channel.connectionHops[0]
+ connection = provableStore.get(connectionPath(connectionId))
+
+ if version != "" {
+ // validate metadata
+ metadata = UnmarshalJSON(version)
+ abortTransactionUnless(metadata.Version === "ics27-1")
+ // all elements in encoding list and tx type list must be supported
+ abortTransactionUnless(IsSupportedEncoding(metadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(metadata.TxType))
+ abortTransactionUnless(metadata.ControllerConnectionId === connectionId)
+ abortTransactionUnless(metadata.HostConnectionId === connection.counterpartyConnectionIdentifier)
+ } else {
+ // construct default metadata
+ metadata = {
+ Version: "ics27-1",
+ ControllerConnectionId: connectionId,
+ HostConnectionId: counterpartyConnectionId,
+ // implementation may choose a default encoding and TxType
+ // e.g. DefaultEncoding=protobuf, DefaultTxType=sdk.MultiMsg
+ Encoding: DefaultEncoding,
+ TxType: DefaultTxType,
+ }
+ version = marshalJSON(metadata)
+ }
+
+ // only open the channel if:
+ // - there is no active channel already set (with status OPEN)
+ // OR
+ // - there is already an active channel (with status CLOSED) AND
+ // the metadata matches exactly the existing metadata in the
+ // version string of the active channel AND the ordering of the
+ // new channel matches the ordering of the active channel.
+ activeChannelId, activeChannelFound = GetActiveChannelID(portId, connectionId)
+ if activeChannelFound {
+ activeChannel = provableStore.get(channelPath(portId, activeChannelId))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(activeChannel.state === CLOSED)
+ previousOrder = activeChannel.order
+ abortTransactionUnless(previousOrder === order)
+ previousMetadata = UnmarshalJSON(activeChannel.version)
+ abortTransactionUnless(previousMetadata === metadata)
+ }
+
+ return version, nil
+}
+```
+
+```typescript
+// Called on Host Chain by Relayer
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string
+): (version: string, err: Error) {
+ // validate port ID
+ abortTransactionUnless(portIdentifier === "icahost")
+
+ // retrieve channel and connection to access connection ID and counterparty connection ID
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ connectionId = channel.connectionHops[0]
+ connection = provableStore.get(connectionPath(connectionId))
+
+ // create the interchain account with the counterpartyPortIdentifier
+ // and the underlying connectionID on the host chain.
+ address = RegisterInterchainAccount(counterpartyPortIdentifier, connectionId)
+
+ // state change to keep track of successfully registered interchain account
+ SetInterchainAccountAddress(counterpartyPortIdentifier, connectionId, address)
+
+ cpMetadata = UnmarshalJSON(counterpartyVersion)
+ // it's not mandatory for the controller to fill in the host connection ID, since
+ // it could not be possible for it to know it. ibc-go's implementation of the
+ // controller does fill it in, but an CosmWasm controller implementation would
+ // not be able. For that reason, the host fills in here its own connection ID.
+ cpMetadata.HostConnectionId = connectionId
+
+ abortTransactionUnless(cpMetadata.Version === "ics27-1")
+ // If encoding or txType requested by initializing chain is not supported by host chain then
+ // fail handshake and abort transaction
+ abortTransactionUnless(IsSupportedEncoding(cpMetadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(cpMetadata.TxType))
+ abortTransactionUnless(cpMetadata.ControllerConnectionId === connection.counterpartyConnectionIdentifier)
+ abortTransactionUnless(cpMetadata.HostConnectionId === connectionId)
+
+ metadata = {
+ "Version": "ics27-1",
+ "ControllerConnectionId": cpMetadata.ControllerConnectionId,
+ "HostConnectionId": cpMetadata.HostConnectionId,
+ "Address": address,
+ "Encoding": cpMetadata.Encoding,
+ "TxType": cpMetadata.TxType,
+ }
+
+ return string(MarshalJSON(metadata)), nil
+}
+```
+
+```typescript
+// Called on Controller Chain by Relayer
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier,
+ counterpartyVersion: string
+) {
+ // retrieve channel and connection to access connection ID and counterparty connection ID
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ connectionId = channel.connectionHops[0]
+ connection = provableStore.get(connectionPath(connectionId))
+
+ // validate counterparty metadata decided by host chain
+ metadata = UnmarshalJSON(version)
+ abortTransactionUnless(metadata.Version === "ics27-1")
+ abortTransactionUnless(IsSupportedEncoding(metadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(metadata.TxType))
+ abortTransactionUnless(metadata.ControllerConnectionId === connectionId)
+ abortTransactionUnless(metadata.HostConnectionId === connection.counterpartyConnectionIdentifier)
+
+ // state change to keep track of successfully registered interchain account
+ SetInterchainAccountAddress(portID, metadata.ControllerConnectionId, metadata.Address)
+ // set the active channel for this owner/interchain account pair
+ SetActiveChannelID(portIdentifier, metadata.ControllerConnectionId, channelIdentifier)
+}
+```
+
+```typescript
+// Called on Host Chain by Relayer
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+
+ // set the active channel for this owner/interchain account pair
+ SetActiveChannelID(channel.counterpartyPortIdentifier, channel.connectionHops[0], channelIdentifier)
+}
+```
+
+```typescript
+// The controller portID must have the format: `icacontroller-{ownerAddress}`
+function validateControllerPortParams(portIdentifier: Identifier) {
+ split(portIdentifier, "-")
+ abortTransactionUnless(portIdentifier[0] === "icacontroller")
+ abortTransactionUnless(IsValidAddress(portIdentifier[1]))
+}
+```
+
+### Closing handshake
+
+```typescript
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // disallow user-initiated channel closing for interchain account channels
+ abortTransactionUnless(FALSE)
+}
+```
+
+```typescript
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+}
+```
+
+### Upgrade handshake
+
+```typescript
+// Called on Controller Chain by Authority
+function onChanUpgradeInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ upgradeSequence: uint64,
+ version: string
+): (version: string, err: Error) {
+ // new version proposed in the upgrade
+ abortTransactionUnless(version !== "")
+ metadata = UnmarshalJSON(version)
+
+ // retrieve the existing channel version.
+ // In ibc-go, for example, this is done using the GetAppVersion
+ // function of the ICS4Wrapper interface.
+ // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ currentMetadata = UnmarshalJSON(channel.version)
+
+ // validate metadata
+ abortTransactionUnless(metadata.Version === "ics27-1")
+ // all elements in encoding list and tx type list must be supported
+ abortTransactionUnless(IsSupportedEncoding(metadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(metadata.TxType))
+
+ // the interchain account address on the host chain
+ // must remain the same after the upgrade.
+ abortTransactionUnless(currentMetadata.Address === metadata.Address)
+
+ // at the moment it is not supported to perform upgrades that
+ // change the connection ID of the controller or host chains.
+ // therefore these connection IDs much remain the same as before.
+ abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId)
+ abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId)
+ // the proposed connection hop must not change
+ abortTransactionUnless(currentMetadata.ControllerConnectionId === connectionHops[0])
+
+ return version, nil
+}
+```
+
+```typescript
+// Called on Host Chain by Relayer
+function onChanUpgradeTry(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ upgradeSequence: uint64,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string
+): (version: string, err: Error) {
+ // validate port ID
+ abortTransactionUnless(portIdentifier === "icahost")
+
+ // upgrade version proposed by counterparty
+ abortTransactionUnless(counterpartyVersion !== "")
+ metadata = UnmarshalJSON(counterpartyVersion)
+
+ // retrieve the existing channel version.
+ // In ibc-go, for example, this is done using the GetAppVersion
+ // function of the ICS4Wrapper interface.
+ // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ currentMetadata = UnmarshalJSON(channel.version)
+
+ // validate metadata
+ abortTransactionUnless(metadata.Version === "ics27-1")
+ // all elements in encoding list and tx type list must be supported
+ abortTransactionUnless(IsSupportedEncoding(metadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(metadata.TxType))
+
+ // the interchain account address on the host chain
+ // must remain the same after the upgrade.
+ abortTransactionUnless(currentMetadata.Address === metadata.Address)
+
+ // at the moment it is not supported to perform upgrades that
+ // change the connection ID of the controller or host chains.
+ // therefore these connection IDs much remain the same as before.
+ abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId)
+ abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId)
+ // the proposed connection hop must not change
+ abortTransactionUnless(currentMetadata.HostConnectionId === connectionHops[0])
+
+ return counterpartyVersion, nil
+}
+```
+
+```typescript
+// Called on Controller Chain by Relayer
+function onChanUpgradeAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyVersion: string
+): Error {
+ // final upgrade version proposed by counterparty
+ abortTransactionUnless(counterpartyVersion !== "")
+ metadata = UnmarshalJSON(counterpartyVersion)
+
+ // retrieve the existing channel version.
+ // In ibc-go, for example, this is done using the GetAppVersion
+ // function of the ICS4Wrapper interface.
+ // See https://github.com/cosmos/ibc-go/blob/ac6300bd857cd2bd6915ae51e67c92848cbfb086/modules/core/05-port/types/module.go#L128-L132
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ currentMetadata = UnmarshalJSON(channel.version)
+
+ // validate metadata
+ abortTransactionUnless(metadata.Version === "ics27-1")
+ // all elements in encoding list and tx type list must be supported
+ abortTransactionUnless(IsSupportedEncoding(metadata.Encoding))
+ abortTransactionUnless(IsSupportedTxType(metadata.TxType))
+
+ // the interchain account address on the host chain
+ // must remain the same after the upgrade.
+ abortTransactionUnless(currentMetadata.Address === metadata.Address)
+
+ // at the moment it is not supported to perform upgrades that
+ // change the connection ID of the controller or host chains.
+ // therefore these connection IDs much remain the same as before.
+ abortTransactionUnless(currentMetadata.ControllerConnectionId === metadata.ControllerConnectionId)
+ abortTransactionUnless(currentMetadata.HostConnectionId === metadata.HostConnectionId)
+
+ return nil
+}
+```
+
+```typescript
+// Called on Controller and Host Chains by Relayer
+function onChanUpgradeOpen(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // no-op
+}
+```
+
+### Packet relay
+
+`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.
+
+```typescript
+// Called on Host Chain by Relayer
+function onRecvPacket(packet Packet) {
+ ack = NewResultAcknowledgement([]byte{byte(1)})
+
+ // only attempt the application logic if the packet data
+ // was successfully decoded
+ switch data.Type {
+ case types.EXECUTE_TX:
+ msgs, err = types.DeserializeTx(data.Data)
+ if err != nil {
+ return NewErrorAcknowledgement(err)
+ }
+
+ // ExecuteTx calls the AuthenticateTx function defined above
+ result, err = ExecuteTx(ctx, packet.sourcePort, packet.destPort, packet.destChannel, msgs)
+ if err != nil {
+ // NOTE: The error string placed in the acknowledgement must be consistent across all
+ // nodes in the network or there will be a fork in the state machine.
+ return NewErrorAcknowledgement(err)
+ }
+
+ // return acknowledgement containing the transaction result after executing on host chain
+ return NewAcknowledgement(result)
+
+ default:
+ return NewErrorAcknowledgement(ErrUnknownDataType)
+ }
+}
+```
+
+`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.
+
+```typescript
+// Called on Controller Chain by Relayer
+function onAcknowledgePacket(
+ packet: Packet,
+ acknowledgement: bytes
+) {
+ // call underlying app's OnAcknowledgementPacket callback
+ // see ICS-30 middleware for more information
+}
+```
+
+```typescript
+// Called on Controller Chain by Relayer
+function onTimeoutPacket(packet: Packet) {
+ // call underlying app's OnTimeoutPacket callback
+ // see ICS-30 middleware for more information
+}
+```
+
+Note that interchain accounts controller modules should not execute any logic upon packet receipt, i.e. the `OnRecvPacket` callback should not be called, and in case it is called, it should simply return an error acknowledgement:
+
+```typescript
+// Called on Controller Chain by Relayer
+function onRecvPacket(packet Packet) {
+ return NewErrorAcknowledgement(ErrInvalidChannelFlow)
+}
+```
+
+### Identifier formats
+
+These are the default formats that the port identifiers on each side of an interchain accounts channel. The controller portID **must** include the owner address so that when a message is sent to the controller module, the sender of the message can be verified against the portID before sending the ICA packet. The controller chain is responsible for proper access control to ensure that the sender of the ICA message has successfully authenticated before the message reaches the controller module.
+
+Controller Port Identifier: optional prefix `icacontroller-` + mandatory `{owner-account-address}`
+
+Host Port Identifier: `icahost`
+
+The `icacontroller-` prefix on the controller port identifier is optional and host chains **must** not enforce that the counterparty port identifier includes it. Controller chains may decide to include it and validate that it is present in their own port identifier.
+
+## Example Implementations
+
+- Implementation of ICS 27 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## Future Improvements
+
+A future version of interchain accounts may be greatly simplified by the introduction of an IBC channel type that is ORDERED but does not close the channel on timeouts, and instead proceeds to accept and receive the next packet. If such a channel type is made available by core IBC, Interchain accounts could require the use of this channel type and remove all logic and state pertaining to "active channels". The metadata format can also be simplified to remove any reference to the underlying connection identifiers.
+
+The "active channel" setting and unsetting is currently necessary to allow interchain account owners to create a new channel in case the current active channel closes during channel timeout. The connection identifiers are part of the metadata to ensure that any new channel that gets opened are established on top of the original connection. All of this logic becomes unnecessary once the channel is ordered **and** unclosable, which can only be achieved by the introduction of a new channel type to core IBC.
+
+## History
+
+Aug 1, 2019 - Concept discussed
+
+Sep 24, 2019 - Draft suggested
+
+Nov 8, 2019 - Major revisions
+
+Dec 2, 2019 - Minor revisions (Add more specific description & Add interchain account on Ethereum)
+
+July 14, 2020 - Major revisions
+
+April 27, 2021 - Redesign of ics27 specification
+
+November 11, 2021 - Update with latest changes from implementation
+
+December 14, 2021 - Revisions to spec based on audits and maintainer reviews
+
+August 1, 2023 - Implemented channel upgrades callbacks
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/README.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/README.mdx
new file mode 100644
index 00000000..6e43c8cf
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/README.mdx
@@ -0,0 +1,74 @@
+---
+ics: 28
+title: Cross-Chain Validation
+stage: draft
+category: IBC/APP
+requires: [25, 26, 20]
+authors:
+ - name: Marius Poke
+ email: marius@informal.systems
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+ - name: Jovan Komatovic
+ email: jovan.komatovic@epfl.ch
+ - name: Cezara Dragoi
+ email: cezara.dragoi@inria.fr
+ - name: Josef Widder
+ email: josef@informal.systems
+created: 2022-06-27
+modified: 2022-12-02
+---
+
+# Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for Cross-Chain Validation (CCV). CCV is the specific IBC level protocol that enables *Interchain Security*, a Cosmos-specific category of *Shared Security*.
+
+At a high level, CCV enables a *provider chain* (e.g., the Cosmos Hub) to provide *security* to multiple *consumer chains*. This means that the validator sets on the consumer chains are chosen from the validator set of the provider chain (for more details, see the [Security Model](/ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts#security-model) section).
+
+The communication between the provider and the consumer chains is done through the IBC protocol over a *unique*, *ordered* channel (one for each consumer chain).
+
+> Throughout this document, we will use the terms chain and blockchain interchangeably.
+
+## Contents
+
+- [Overview and Basic Concepts](/ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts)
+- [System Model and Properties](/ibc/next/spec/app/ics-028-cross-chain-validation/system_model_and_properties)
+- [Technical Specification: Data Structures and Methods](/ibc/next/spec/app/ics-028-cross-chain-validation/technical_specification)
+ - [Data Structures](/ibc/next/spec/app/ics-028-cross-chain-validation/data_structures)
+ - [Methods](/ibc/next/spec/app/ics-028-cross-chain-validation/methods)
+
+{/*
+## Backwards Compatibility
+
+(discussion of compatibility or lack thereof with previous standards)
+
+## Forwards Compatibility
+
+*/}
+
+## Example Implementations
+
+- Interchain Security [Go implementation](https://github.com/cosmos/interchain-security).
+
+{/*
+## Other Implementations
+
+(links to or descriptions of other implementations)
+
+*/}
+
+## History
+
+Jun 27, 2022 - Draft written
+
+Aug 3, 2022 - Revision of *Bond-Based Consumer Voting Power* property
+
+Aug 29, 2022 - Notify Staking module of matured unbondings in `EndBlock()`
+
+Dec 2, 2022 - Enable existing chains to become consumer chains
+
+Dec 7, 2022 - Add provider-based timeouts
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/data_structures.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/data_structures.mdx
new file mode 100644
index 00000000..61bd92bd
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/data_structures.mdx
@@ -0,0 +1,340 @@
+---
+title: "CCV: Technical Specification - Data Structures"
+---
+
+{/* omit in toc */}
+
+{/* omit in toc */}
+## Outline
+
+- [External Data Structures](#external-data-structures)
+- [CCV Data Structures](#ccv-data-structures)
+- [CCV Packets](#ccv-packets)
+- [CCV State](#ccv-state)
+ - [State on Provider Chain](#state-on-provider-chain)
+ - [State on Consumer Chain](#state-on-consumer-chain)
+
+## External Data Structures
+
+[↑ Back to Outline](#outline)
+
+This section describes external data structures used by the CCV module.
+
+The CCV module uses the ABCI `ValidatorUpdate` data structure, which consists of a validator and its power (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#data-types)), i.e.,
+
+```typescript
+interface ValidatorUpdate {
+ pubKey: PublicKey
+ power: int64
+}
+```
+
+The provider chain sends to the consumer chain a list of `ValidatorUpdate`s, containing an entry for every validator that had its power updated.
+
+The data structures required for creating clients (i.e., `ClientState`, `ConsensusState`) are defined in [ICS 2](../../core/ics-002-client-semantics).
+In the context of CCV, every chain is uniquely defined by their chain ID and the validator set.
+Thus, CCV requires the `ClientState` to contain the chain ID and the `ConsensusState` for a particular height to contain the validator set at that height.
+In addition, the `ClientState` should contain the `UnbondingPeriod`.
+For an example, take a look at the `ClientState` and `ConsensusState` defined in [ICS 7](../../client/ics-007-tendermint-client).
+
+## CCV Data Structures
+
+[↑ Back to Outline](#outline)
+
+The CCV module is initialized through the `InitGenesis` method when the chain is first started. The initialization is done from a genesis state. This is the case for both provider and consumer chains:
+
+- On the provider chain, the genesis state is described by the following interface:
+
+ ```typescript
+ interface ProviderGenesisState {
+ // a list of existing consumer chains
+ consumerStates: [ConsumerState]
+ }
+ ```
+
+ with `ConsumerState` defined as
+
+ ```typescript
+ interface ConsumerState {
+ chainId: string
+ channelId: Identifier
+ }
+ ```
+
+- On the consumer chain, the genesis state is described by the following interface:
+
+ ```typescript
+ interface ConsumerGenesisState {
+ preCCV: Bool
+ unbondingPeriod: Duration
+ connId: Identifier
+ providerClientState: ClientState
+ providerConsensusState: ConsensusState
+ counterpartyClientId: Identifier
+ initialValSet: [ValidatorUpdate]
+ transferChannelId: Identifier
+ }
+ ```
+
+ - `preCCV` is a flag indicating whether the consumer CCV module starts in pre-CCV state.
+ In pre-CCV state the consumer CCV module MUST NOT pass validator updates to the underlying consensus engine.
+ If `preCCV == true`, then `connId` must be set.
+ - `unbondingPeriod` is the unbonding period on the consumer chain.
+ - `connId` is the ID of the connection end on the consumer chain on top of which the CCV channel will be established.
+ If `connId == ""`, a new client of the provider chain and a new connection on top of this client are created.
+ - `providerClientState` is the client state used to create a new client of the provider chain (as defined in [ICS 2](../../core/ics-002-client-semantics)).
+ If `connId != ""`, then `providerClientState` is ignored.
+ - `providerConsensusState` is the consensus state used to create a new client of the provider chain (as defined in [ICS 2](../../core/ics-002-client-semantics)).
+ If `connId != ""`, then `providerConsensusState` is ignored.
+ - `counterpartyClientId` is the ID of the client of the consumer chain on the provider chain.
+ Note that `counterpartyClientId` is only needed to allow the consumer CCV module to initiate the connection opening handshake.
+ If `connId != ""`, then `counterpartyClientId` is ignored.
+ - `initialValSet` is the first validator set that will start validating on this consumer chain.
+ - `transferChannelId` is the ID of a token transfer channel (as defined in [ICS 20](../../app/ics-020-fungible-token-transfer)) used for the Reward Distribution sub-protocol.
+ If `transferChannelId == ""`, a new token transfer channel is created on top of the same connection as the CCV channel.
+
+The provider CCV module handles governance proposals to add new consumer chains and to remove existing consumer chains.
+While the structure of governance proposals is specific to every ABCI application (for an example, see the `Proposal` interface in the [Governance module documentation](/sdk/v0.53/build/modules/gov) of Cosmos SDK),
+this specification expects the following fields to be part of the proposals to add new consumer chains (i.e., `ConsumerAdditionProposal`) and to remove existing ones (i.e., `ConsumerRemovalProposal`):
+
+ ```typescript
+ interface ConsumerAdditionProposal {
+ chainId: string
+ spawnTime: Timestamp
+ connId: Identifier
+ unbondingPeriod: Duration
+ transferChannelId: Identifier
+ lockUnbondingOnTimeout: Bool
+ }
+ ```
+
+- `chainId` is the proposed chain ID of the new consumer chain. It must be different from all other consumer chain IDs of the executing provider chain.
+- `spawnTime` is the time on the provider chain at which the consumer chain genesis is finalized and all validators are responsible to start their consumer chain validator node.
+- `connId` is the ID of the connection end on the provider chain on top of which the CCV channel will be established.
+ If `connId == ""`, a new client of the consumer chain and a new connection on top of this client are created.
+ Note that a sovereign chain can transition to a consumer chain while maintaining existing IBC channels to other chains by providing a valid `connId`.
+- `unbondingPeriod` is the unbonding period on the consumer chain.
+- `transferChannelId` is the ID of a token transfer channel (as defined in [ICS 20](../../app/ics-020-fungible-token-transfer)) used for the Reward Distribution sub-protocol.
+ If `transferChannelId == ""`, a new token transfer channel is created on top of the same connection as the CCV channel.
+ Note that `transferChannelId` is the ID of the channel end on the consumer chain.
+- `lockUnbondingOnTimeout` is a boolean value that indicates whether the funds corresponding to the outstanding unbonding operations are to be released in case of a timeout.
+ If `lockUnbondingOnTimeout == true`, a governance proposal to stop the timed out consumer chain would be necessary to release the locked funds.
+
+ ```typescript
+ interface ConsumerRemovalProposal {
+ chainId: string
+ stopTime: Timestamp
+ }
+ ```
+
+- `chainId` is the chain ID of the consumer chain to be removed. It must be the ID of an existing consumer chain of the executing provider chain.
+- `stopTime` is the time on the provider chain at which all validators are responsible to stop their consumer chain validator node.
+
+During the CCV channel opening handshake, the provider chain adds the address of its distribution module account to the channel version as metadata (as described in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#definitions)).
+The metadata structure is described by the following interface:
+
+```typescript
+interface CCVHandshakeMetadata {
+ providerDistributionAccount: string // the account's address
+ version: string
+}
+```
+
+This specification assumes that the provider CCV module has access to the address of the distribution module account through the `GetDistributionAccountAddress()` method. For an example, take a look at the [auth module](/sdk/v0.53/build/modules/auth) of Cosmos SDK.
+
+## CCV Packets
+
+[↑ Back to Outline](#outline)
+
+The structure of the packets sent through the CCV channel is defined by the `Packet` interface in [ICS 4](../../core/ics-004-channel-and-packet-semantics).
+The following packet data types are required by the CCV module:
+
+- `VSCPacketData` contains a list of validator updates, i.e.,
+
+ ```typescript
+ interface VSCPacketData {
+ // the id of this VSC
+ id: uint64
+ // validator updates
+ updates: [ValidatorUpdate]
+ // downtime slash requests acknowledgements,
+ // i.e., list of validator addresses
+ downtimeSlashAcks: [string]
+ }
+ ```
+
+- `VSCMaturedPacketData` contains the ID of the VSC that reached maturity, i.e.,
+
+ ```typescript
+ interface VSCMaturedPacketData {
+ id: uint64 // the id of the VSC that reached maturity
+ }
+ ```
+
+- `SlashPacketData` contains a request to slash a validator, i.e.,
+
+ ```typescript
+ interface SlashPacketData {
+ valAddress: string // validator address, i.e., the hash of its public key
+ valPower: int64
+ vscId: uint64
+ downtime: Bool
+ }
+ ```
+
+> Note that for brevity we use e.g., `VSCPacket` to refer to a packet with `VSCPacketData` as its data.
+
+Packets are acknowledged by the remote side by sending back an `Acknowledgement` that contains either a result (in case of success) or an error (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#acknowledgement-envelope)).
+The following acknowledgement types are required by the CCV module:
+
+```typescript
+type VSCPacketAcknowledgement = VSCPacketSuccess | VSCPacketError;
+type VSCMaturedPacketAcknowledgement = VSCMaturedPacketSuccess | VSCMaturedPacketError;
+type SlashPacketAcknowledgement = SlashPacketSuccess | SlashPacketError;
+type PacketAcknowledgement = PacketSuccess | PacketError; // general ack
+```
+
+## CCV State
+
+[↑ Back to Outline](#outline)
+
+This section describes the internal state of the CCV module. For simplicity, the state is described by a set of variables; for each variable, both the type and a brief description is provided. In practice, all the state (except for hardcoded constants, e.g., `ProviderPortId`) is stored in a key/value store (KVS). The host state machine provides a KVS interface with three functions, i.e., `get()`, `set()`, and `delete()` (as defined in [ICS 24](../../core/ics-024-host-requirements)).
+
+- `ccvVersion = "ccv-1"` is the CCV expected version. Both the provider and the consumer chains need to agree on this version.
+- `zeroTimeoutHeight = {0,0}` is the `timeoutHeight` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) used by CCV for sending packets. Note that CCV uses `ccvTimeoutTimestamp` for sending CCV packets and `transferTimeoutTimestamp` for transferring tokens.
+- `ccvTimeoutTimestamp: uint64` is the `timeoutTimestamp` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) for sending CCV packets. The CCV protocol is responsible of setting `ccvTimeoutTimestamp` such that the *Correct Relayer* assumption is feasible.
+- `transferTimeoutTimestamp: uint64` is the `timeoutTimestamp` (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)) for transferring tokens.
+
+### State on Provider Chain
+
+[↑ Back to Outline](#outline)
+
+- `ProviderPortId = "provider"` is the port ID the provider CCV module is expected to bind to.
+- `initTimeout: uint64` is the maximum time duration the Channel Initialization subprotocol may execute,
+ i.e., for any consumer chain, if the CCV channel is not established within `initTimeout` since the consumer chain was registered, then the consumer chain is removed.
+- `vscTimeout: uint64` is the maximum time duration between sending any `VSCPacket` to any consumer chain and receiving the corresponding `VSCMaturedPacket`, without timing out the consumer chain and consequently removing it.
+- `pendingConsumerAdditionProposals: [ConsumerAdditionProposal]` is a list of pending governance proposals to add new consumer chains.
+- `pendingConsumerRemovalProposals: [ConsumerRemovalProposal]` is a list of pending governance proposals to remove existing consumer chains.
+ Both lists of pending governance proposals expose the following interface:
+
+```typescript
+ interface [Proposal] {
+ // append a proposal to the list; the list is modified
+ Append(p: Proposal)
+
+ // remove a proposal from the list; the list is modified
+ Remove(p: Proposal)
+ }
+ ```
+
+- `lockUnbondingOnTimeout: Map` is a mapping from consumer chain IDs to the boolean values indicating whether the funds corresponding to the in progress unbonding operations are to be released in case of a timeout.
+- `chainToClient: Map` is a mapping from consumer chain IDs to the associated client IDs.
+- `chainToConnection: Map` is a mapping from consumer chain IDs to the associated connection IDs.
+- `chainToChannel: Map` is a mapping from consumer chain IDs to the CCV channel IDs.
+- `channelToChain: Map` is a mapping from CCV channel IDs to consumer chain IDs.
+- `initTimeoutTimestamps: Map` is a mapping from consumer chain IDs to init timeout timestamps, see `initTimeout`.
+- `pendingVSCPackets: Map` is a mapping from consumer chain IDs to a list of pending `VSCPacketData`s that must be sent to the consumer chain once the CCV channel is established. The map exposes the following interface:
+
+ ```typescript
+ interface Map {
+ // append a VSCPacketData to the list mapped to chainId;
+ // the list is modified
+ Append(chainId: string, data: VSCPacketData)
+
+ // remove all the VSCPacketData mapped to chainId;
+ // the list is modified
+ Remove(chainId: string)
+ }
+- `vscId: uint64` is a monotonic strictly increasing and positive ID that is used to uniquely identify the VSCs sent to the consumer chains.
+ Note that `0` is used as a special ID for the mapping from consumer heights to provider heights.
+- `vscSendTimestamps: Map<(string, uint64), uint64>` is a mapping from `(chainId, vscId)` tuples to the timestamps of sending `VSCPacket`s.
+- `initialHeights: Map` is a mapping from consumer chain IDs to the heights on the provider chain.
+ For every consumer chain, the mapping stores the height when the CCV channel to that consumer chain is established.
+ Note that the provider validator set at this height matches the validator set at the height when the first VSC is provided to that consumer chain.
+ It enables the mapping from consumer heights to provider heights.
+- `VSCtoH: Map` is a mapping from VSC IDs to heights on the provider chain. It enables the mapping from consumer heights to provider heights,
+ i.e., the voting power at height `VSCtoH[id]` on the provider chain was last updated by the validator updates contained in the VSC with ID `id`.
+- `unbondingOps: Map` is a mapping that enables accessing for every unbonding operation the list of consumer chains that are still unbonding. When unbonding operations are initiated, the Staking module calls the `AfterUnbondingInitiated()` [hook](./methods#ccv-pcf-hook-afubopcr1)); this leads to the creation of a new `UnbondingOperation`, which is defined as
+
+ ```typescript
+ interface UnbondingOperation {
+ id: uint64
+ // list of consumer chain IDs that are still unbonding
+ unbondingChainIds: [string]
+ }
+ ```
+
+- `vscToUnbondingOps: Map<(string, uint64), [uint64]>` is a mapping from `(chainId, vscId)` tuples to a list of unbonding operation IDs.
+ It enables the provider CCV module to match a `VSCMaturedPacket{vscId}`, received from a consumer chain with `chainId`, with the corresponding unbonding operations.
+ As a result, `chainId` can be removed from the list of consumer chains that are still unbonding these operations.
+ For more details see how received `VSCMaturedPacket`s [are handled](./methods#ccv-pcf-rcvmat1).
+- `maturedUnbondingOps: [uint64]` is a list of IDs of matured unbonding operations (from the perspective of the consumer chains), for which notifications can be sent to the Staking module (see `stakingKeeper.UnbondingCanComplete`).
+ Note that `maturedUnbondingOps` is emptied at the end of each block.
+- `downtimeSlashRequests: Map` is a mapping from `chainId`s to lists of validator addresses,
+ i.e., `downtimeSlashRequests[chainId]` contains all the validator addresses for which the provider chain received slash requests for downtime from the consumer chain with `chainId`.
+
+### State on Consumer Chain
+
+[↑ Back to Outline](#outline)
+
+- `ConsumerPortId = "consumer"` is the port ID the consumer CCV module is expected to bind to.
+- `ConsumerUnbondingPeriod: Duration` is the unbonding period on the consumer chain.
+- `preCCV: Bool` is a flag indicating whether the consumer CCV module starts in pre-CCV state.
+ In pre-CCV state, the consumer CCV module MUST NOT pass validator updates to the underlying consensus engine.
+- `providerClientId: Identifier` identifies the client of the provider chain (on the consumer chain) that the CCV channel is build upon.
+- `providerChannel: Identifier` identifies the consumer's channel end of the CCV channel.
+- `ccvValidatorSet: ` is a mapping that stores the validators in the validator set of the consumer chain.
+- `receivedVSCs: [VSCPacketData]` is a list of data items (i.e., `VSCPacketData`) received in `VSCPacket`s that are not yet applied.
+- `HtoVSC: Map` is a mapping from consumer chain heights to VSC IDs. It enables the mapping from consumer heights to provider heights., i.e.,
+ - if `HtoVSC[h] == 0`, then the voting power on the consumer chain at height `h` was setup at genesis during Channel Initialization;
+ - otherwise, the voting power on the consumer chain at height `h` was updated by the VSC with ID `HtoVSC[h]`.
+- `maturingVSCs: [(uint64, uint64)]` is a list of `(id, ts)` tuples, where `id` is the ID of a VSC received via a `VSCPacket` and `ts` is the timestamp at which the VSC reaches maturity on the consumer chain.
+ The list is used to keep track of when unbonding operations are matured on the consumer chain. It exposes the following interface:
+
+ ```typescript
+ interface [(uint64, uint64)] {
+ // add a VSC id with its maturity timestamp to the list;
+ // the list is modified
+ Add(id: uint64, ts: uint64)
+
+ // return the list sorted by the maturity timestamps;
+ // the original list is not modified
+ SortedByMaturityTime(): [(uint64, uint64)]
+
+ // remove (id, ts) from the list;
+ // the list is modified
+ Remove(id: uint64, ts: uint64)
+ }
+ ```
+
+- `pendingSlashRequests: [SlashRequest]` is a list of pending `SlashRequest`s that must be sent to the provider chain once the CCV channel is established. A `SlashRequest` consist of a `SlashPacketData` and a flag indicating whether the request is for downtime slashing. The list exposes the following interface:
+
+ ```typescript
+ interface SlashRequest {
+ data: SlashPacketData
+ downtime: Bool
+ }
+ interface [SlashRequest] {
+ // append a SlashRequest to the list;
+ // the list is modified
+ Append(data: SlashRequest)
+
+ // return the reverse list, i.e., latest SlashRequest first;
+ // the original list is not modified
+ Reverse(): [SlashRequest]
+
+ // remove all the SlashRequest;
+ // the list is modified
+ RemoveAll()
+ }
+ ```
+
+- `outstandingDowntime: ` is a mapping from validator addresses to boolean values.
+ `outstandingDowntime[valAddr] == TRUE` entails that the consumer chain sent a request to slash for downtime the validator with address `valAddr`.
+ `outstandingDowntime[valAddr]` is set to false once the consumer chain receives a confirmation that the downtime slash request was received by the provider chain, i.e., a `VSCPacket` that contains `valAddr` in `downtimeSlashAcks`.
+ The mapping enables the consumer CCV module to avoid sending to the provider chain multiple slashing requests for the same downtime infraction.
+- `providerDistributionAccount: string` is the address of the distribution module account on the provider chain. It enables the consumer chain to transfer rewards to the provider chain.
+- `distributionChannelId: Identifier` is the ID of the distribution token transfer channel used for sending rewards to the provider chain.
+- `BlocksPerDistributionTransfer: int64` is the interval (in number of blocks) between two distribution token transfers.
+- `lastDistributionTransferHeight: Height` is the block height of the last distribution token transfer.
+- `ccvAccount: string` is the address of the CCV module account where a fraction of the consumer chain rewards are collected before being transferred to the provider chain.
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.excalidraw
new file mode 100644
index 00000000..9a4cf851
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.excalidraw
@@ -0,0 +1,1448 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "text",
+ "version": 1005,
+ "versionNonce": 942012557,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1391.0634920634916,
+ "y": 1852.9047619047617,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 361,
+ "height": 35,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717847398,
+ "link": null,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "CCV - Reward Distribution",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV - Reward Distribution"
+ },
+ {
+ "type": "text",
+ "version": 1473,
+ "versionNonce": 1279271715,
+ "isDeleted": false,
+ "id": "yqi6dzvpzblq17ysowkTo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1184.1995040738243,
+ "y": 1919.7150027318748,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 138,
+ "height": 26,
+ "seed": 578772677,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717830267,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1700,
+ "versionNonce": 380537603,
+ "isDeleted": false,
+ "id": "Y19fC1uuzabCFyV0s97Ip",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1774.6009368748905,
+ "y": 1919.7150027318748,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 148,
+ "height": 26,
+ "seed": 1161289003,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717824957,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chain"
+ },
+ {
+ "type": "text",
+ "version": 1418,
+ "versionNonce": 856000099,
+ "isDeleted": false,
+ "id": "A_uPQosGByCb18jg5F8J3",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1608.6178858721328,
+ "y": 1957.1879109167996,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 1240462166,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648717754192,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "arrow",
+ "version": 3989,
+ "versionNonce": 2125688557,
+ "isDeleted": false,
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1551.1900175267103,
+ "y": 2344.842556707134,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 128.79181255561082,
+ "height": 0.08876270859309443,
+ "seed": 564068565,
+ "groupIds": [
+ "kCy-i3wrt_ZLgIeQ4otUP"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648718352186,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -128.79181255561082,
+ -0.08876270859309443
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2037,
+ "versionNonce": 196149859,
+ "isDeleted": false,
+ "id": "REPxg78R4E5rz3sm5KHSv",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1409.5528637544644,
+ "y": 2302.708121633686,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 146,
+ "height": 25,
+ "seed": 208528123,
+ "groupIds": [
+ "kCy-i3wrt_ZLgIeQ4otUP"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718352186,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "TokenTransfer",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "TokenTransfer"
+ },
+ {
+ "id": "IjbvMm7oV9sVgP7WlGh5E",
+ "type": "line",
+ "x": 1799.9264992474484,
+ "y": 2096.174492510282,
+ "width": 0.4985622223937298,
+ "height": 33.65304102189839,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 4,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "seed": 806893027,
+ "version": 108,
+ "versionNonce": 348679437,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1648718383497,
+ "link": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.4985622223937298,
+ 33.65304102189839
+ ]
+ ],
+ "lastCommittedPoint": null,
+ "startBinding": null,
+ "endBinding": null,
+ "startArrowhead": null,
+ "endArrowhead": null
+ },
+ {
+ "type": "rectangle",
+ "version": 3280,
+ "versionNonce": 1529278413,
+ "isDeleted": false,
+ "id": "34mJiRUfZv_whlGRmiJJO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1580.7964573007043,
+ "y": 1993.826747397683,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 438,
+ "height": 90.41269841269803,
+ "seed": 906912827,
+ "groupIds": [
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718365618,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1668,
+ "versionNonce": 47873923,
+ "isDeleted": false,
+ "id": "m_vQSEBRga8RR7VuL27UY",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1821.73693349118,
+ "y": 1999.4973823183186,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 109,
+ "height": 50,
+ "seed": 765969819,
+ "groupIds": [
+ "lNYd5uxp6Xm5xLMU-zrby",
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "tvGd783eUc39eNWP1L7-j",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718365618,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "fraction of\nrewards",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "fraction of\nrewards"
+ },
+ {
+ "type": "arrow",
+ "version": 3963,
+ "versionNonce": 229731885,
+ "isDeleted": false,
+ "id": "tvGd783eUc39eNWP1L7-j",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1932.8019559784343,
+ "y": 2060.997674669792,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 125.15697875770252,
+ "height": 1.0514920517930477,
+ "seed": 321384565,
+ "groupIds": [
+ "Uvbj_Wiv_4OirTlk_pHjj",
+ "lNYd5uxp6Xm5xLMU-zrby",
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648718365618,
+ "link": null,
+ "startBinding": {
+ "elementId": "m_vQSEBRga8RR7VuL27UY",
+ "focus": -1.415085392769348,
+ "gap": 11.50029235147349
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -125.15697875770252,
+ -1.0514920517930477
+ ]
+ ]
+ },
+ {
+ "id": "xbQJbKMN6ij9ve7OviLr7",
+ "type": "rectangle",
+ "x": 1599.902692012232,
+ "y": 2035.4081490120375,
+ "width": 188,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 210145315,
+ "version": 332,
+ "versionNonce": 1528635533,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "type": "text",
+ "id": "CYcPnlANLHU04SN9990JF"
+ }
+ ],
+ "updated": 1648718365618,
+ "link": null
+ },
+ {
+ "id": "CYcPnlANLHU04SN9990JF",
+ "type": "text",
+ "x": 1604.902692012232,
+ "y": 2041.4081490120375,
+ "width": 178,
+ "height": 23,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "sharp",
+ "seed": 15459693,
+ "version": 299,
+ "versionNonce": 1227668163,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1648718365618,
+ "link": null,
+ "text": "$ CCV account",
+ "fontSize": 20,
+ "fontFamily": 1,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "baseline": 17,
+ "containerId": "xbQJbKMN6ij9ve7OviLr7",
+ "originalText": "$ CCV account"
+ },
+ {
+ "type": "text",
+ "version": 1938,
+ "versionNonce": 958629613,
+ "isDeleted": false,
+ "id": "Oz5lUjmcWXSCVOUr-aDA6",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1960.8164836480091,
+ "y": 1999.0985727945085,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 756640341,
+ "groupIds": [
+ "dM4n286bGb7i0G9C3JQhZ"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718365618,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc"
+ },
+ {
+ "type": "rectangle",
+ "version": 3445,
+ "versionNonce": 32402019,
+ "isDeleted": false,
+ "id": "smdUW8hJpPhBv4aV5ay_q",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1580.7964573007043,
+ "y": 2266.901858466779,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 438,
+ "height": 90.41269841269803,
+ "seed": 1103023629,
+ "groupIds": [
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718365618,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1833,
+ "versionNonce": 709763405,
+ "isDeleted": false,
+ "id": "F6tHqrlehaqv4NgVfyYod",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1821.73693349118,
+ "y": 2272.5724933874144,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 109,
+ "height": 50,
+ "seed": 1491510595,
+ "groupIds": [
+ "iVaTc7j72nXI1Q2rCU61q",
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "J8lhUtkiur7yuJP0IVsWe",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718365618,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "fraction of\nrewards",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "fraction of\nrewards"
+ },
+ {
+ "type": "arrow",
+ "version": 4359,
+ "versionNonce": 35202563,
+ "isDeleted": false,
+ "id": "J8lhUtkiur7yuJP0IVsWe",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1932.8019559784343,
+ "y": 2334.072785738888,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 125.15697875770252,
+ "height": 1.0514920517930477,
+ "seed": 171148397,
+ "groupIds": [
+ "pplowjM61J9zLv8WW_Iet",
+ "iVaTc7j72nXI1Q2rCU61q",
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648718365618,
+ "link": null,
+ "startBinding": {
+ "elementId": "F6tHqrlehaqv4NgVfyYod",
+ "focus": -1.415085392769348,
+ "gap": 11.50029235147349
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -125.15697875770252,
+ -1.0514920517930477
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 494,
+ "versionNonce": 279670179,
+ "isDeleted": false,
+ "id": "Alaps4d4CIl7xNCwR5rzQ",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1599.902692012232,
+ "y": 2308.4832600811333,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 188,
+ "height": 35,
+ "seed": 134677731,
+ "groupIds": [
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "ew0ofctlhOyFp3fOfssPc",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "ew0ofctlhOyFp3fOfssPc"
+ }
+ ],
+ "updated": 1648718365619,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 464,
+ "versionNonce": 876999181,
+ "isDeleted": false,
+ "id": "ew0ofctlhOyFp3fOfssPc",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1604.902692012232,
+ "y": 2314.4832600811333,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 23,
+ "seed": 595750605,
+ "groupIds": [
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718365619,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "$ CCV account",
+ "baseline": 17,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "Alaps4d4CIl7xNCwR5rzQ",
+ "originalText": "$ CCV account"
+ },
+ {
+ "type": "text",
+ "version": 2105,
+ "versionNonce": 282313027,
+ "isDeleted": false,
+ "id": "qKtTARj56MbaSPbxOCPJk",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1959.8164836480091,
+ "y": 2272.1736838636043,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 523923587,
+ "groupIds": [
+ "pZnJ-ggCgfXjtpihWQO_h"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718365619,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc+x",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc+x"
+ },
+ {
+ "type": "rectangle",
+ "version": 576,
+ "versionNonce": 598161635,
+ "isDeleted": false,
+ "id": "GLbhbcWSEyuebylqDnpU2",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1187.9667306872643,
+ "y": 2288.408149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 188,
+ "height": 57,
+ "seed": 1340530701,
+ "groupIds": [
+ "VtmzX2Iudw6NpYzBxjnOR",
+ "0Xag4f3CHNBceQxWD5THH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "v7H0D2SFSeTqd98kFr9Tn",
+ "type": "text"
+ },
+ {
+ "id": "v7H0D2SFSeTqd98kFr9Tn",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "v7H0D2SFSeTqd98kFr9Tn"
+ }
+ ],
+ "updated": 1648718352186,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 550,
+ "versionNonce": 942635725,
+ "isDeleted": false,
+ "id": "v7H0D2SFSeTqd98kFr9Tn",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1192.9667306872643,
+ "y": 2293.408149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 46,
+ "seed": 780186435,
+ "groupIds": [
+ "VtmzX2Iudw6NpYzBxjnOR",
+ "0Xag4f3CHNBceQxWD5THH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718352186,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "$ distribution \naccount",
+ "baseline": 40,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "GLbhbcWSEyuebylqDnpU2",
+ "originalText": "$ distribution account"
+ },
+ {
+ "type": "rectangle",
+ "version": 3481,
+ "versionNonce": 1085547651,
+ "isDeleted": false,
+ "id": "AkC-Q6AHwm6Ua2co050XG",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1105.9667306872643,
+ "y": 2267.868466472355,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 288.25,
+ "height": 88.57936507936469,
+ "seed": 1841008003,
+ "groupIds": [
+ "0Xag4f3CHNBceQxWD5THH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718352186,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1836,
+ "versionNonce": 1869650221,
+ "isDeleted": false,
+ "id": "ukJYsO2TyJCEXc5fyeEt_",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1111.4667306872643,
+ "y": 2275.908149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1647435235,
+ "groupIds": [
+ "0Xag4f3CHNBceQxWD5THH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718352186,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp2",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp2"
+ },
+ {
+ "type": "arrow",
+ "version": 3920,
+ "versionNonce": 563157581,
+ "isDeleted": false,
+ "id": "t6EBWr_9oI5RhHG2G77KC",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1557.4055642998305,
+ "y": 2074.3425567071336,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 128.79181255561082,
+ "height": 0.08876270859309443,
+ "seed": 1421736451,
+ "groupIds": [
+ "jL3JwgrGJGsJ9iKIcLo8U"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648717806780,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -128.79181255561082,
+ -0.08876270859309443
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1968,
+ "versionNonce": 1331576067,
+ "isDeleted": false,
+ "id": "Q1QsNgKGpkQte_Ty3Gq5x",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1415.7684105275844,
+ "y": 2032.2081216336865,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 146,
+ "height": 25,
+ "seed": 1505287085,
+ "groupIds": [
+ "jL3JwgrGJGsJ9iKIcLo8U"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717806780,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "TokenTransfer",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "TokenTransfer"
+ },
+ {
+ "type": "rectangle",
+ "version": 509,
+ "versionNonce": 932698541,
+ "isDeleted": false,
+ "id": "s_dUdYMbfAgY-wx64-cZy",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1187.9667306872643,
+ "y": 2017.908149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 188,
+ "height": 57,
+ "seed": 1603649955,
+ "groupIds": [
+ "XrGNgTVaQYbbqnVbqe1WW",
+ "X2TBHJO-i4ICHszzaNf9v"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "HgpbjFpWkkElb6JHK30jf",
+ "type": "text"
+ },
+ {
+ "id": "HgpbjFpWkkElb6JHK30jf",
+ "type": "text"
+ },
+ {
+ "id": "HgpbjFpWkkElb6JHK30jf",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "HgpbjFpWkkElb6JHK30jf"
+ }
+ ],
+ "updated": 1648717890359,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 482,
+ "versionNonce": 296592291,
+ "isDeleted": false,
+ "id": "HgpbjFpWkkElb6JHK30jf",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1192.9667306872643,
+ "y": 2022.908149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 46,
+ "seed": 1591401997,
+ "groupIds": [
+ "XrGNgTVaQYbbqnVbqe1WW",
+ "X2TBHJO-i4ICHszzaNf9v"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717890359,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "$ distribution \naccount",
+ "baseline": 40,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "s_dUdYMbfAgY-wx64-cZy",
+ "originalText": "$ distribution account"
+ },
+ {
+ "type": "rectangle",
+ "version": 3413,
+ "versionNonce": 1680481293,
+ "isDeleted": false,
+ "id": "nAD6yE-Il7_lA6MHEDRRU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1105.9667306872643,
+ "y": 1997.368466472355,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 288.25,
+ "height": 88.57936507936469,
+ "seed": 928224579,
+ "groupIds": [
+ "X2TBHJO-i4ICHszzaNf9v"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648717890359,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1765,
+ "versionNonce": 1612658499,
+ "isDeleted": false,
+ "id": "24NFD0EAJBhRkLJaPjCB7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1111.4667306872643,
+ "y": 2005.408149012037,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1229821037,
+ "groupIds": [
+ "X2TBHJO-i4ICHszzaNf9v"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648717890359,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp1",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp1"
+ },
+ {
+ "type": "rectangle",
+ "version": 3523,
+ "versionNonce": 1093005251,
+ "isDeleted": false,
+ "id": "5UhO9vf3ic8tfV-ct-H4a",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1580.7964573007043,
+ "y": 2141.4520030011813,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 438,
+ "height": 67.91269841269799,
+ "seed": 1223050061,
+ "groupIds": [
+ "kEhD5pp9ZyDcmuVHj7jXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718382746,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1929,
+ "versionNonce": 1078206957,
+ "isDeleted": false,
+ "id": "ORXBeHtSMUVrEyiFaIik_",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1804.48693349118,
+ "y": 2149.6226379218165,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 196,
+ "height": 25,
+ "seed": 1376527363,
+ "groupIds": [
+ "EZZbZ1FR2RI1InChFO1wi",
+ "kEhD5pp9ZyDcmuVHj7jXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "0hQr8j4kqSD8M3pL0Qbq9",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1648718382746,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "fraction of rewards",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "fraction of rewards"
+ },
+ {
+ "type": "arrow",
+ "version": 4612,
+ "versionNonce": 1861812067,
+ "isDeleted": false,
+ "id": "0hQr8j4kqSD8M3pL0Qbq9",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1985.0603540692218,
+ "y": 2190.3258903405326,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 177.41537684849004,
+ "height": 0.2544521190352498,
+ "seed": 1060786605,
+ "groupIds": [
+ "fPmYjanoLwZxiv--xyo7a",
+ "EZZbZ1FR2RI1InChFO1wi",
+ "kEhD5pp9ZyDcmuVHj7jXo"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648718382746,
+ "link": null,
+ "startBinding": {
+ "elementId": "ORXBeHtSMUVrEyiFaIik_",
+ "focus": -2.221803403364259,
+ "gap": 15.703252418716147
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -177.41537684849004,
+ -0.2544521190352498
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 563,
+ "versionNonce": 2064194307,
+ "isDeleted": false,
+ "id": "AaZnP-uk9pLjwUgVCuPxX",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1599.902692012232,
+ "y": 2160.5334046155363,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 188,
+ "height": 35,
+ "seed": 630107043,
+ "groupIds": [
+ "kEhD5pp9ZyDcmuVHj7jXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "y72sz2bjJqP1nhMBGOJmw",
+ "type": "text"
+ },
+ {
+ "id": "y72sz2bjJqP1nhMBGOJmw",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "y72sz2bjJqP1nhMBGOJmw"
+ }
+ ],
+ "updated": 1648718382747,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 532,
+ "versionNonce": 1777093293,
+ "isDeleted": false,
+ "id": "y72sz2bjJqP1nhMBGOJmw",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1604.902692012232,
+ "y": 2166.5334046155363,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 23,
+ "seed": 1086166029,
+ "groupIds": [
+ "kEhD5pp9ZyDcmuVHj7jXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1648718382747,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "$ CCV account",
+ "baseline": 17,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "AaZnP-uk9pLjwUgVCuPxX",
+ "originalText": "$ CCV account"
+ },
+ {
+ "type": "line",
+ "version": 162,
+ "versionNonce": 1275754051,
+ "isDeleted": false,
+ "id": "LWfeCA_MHMz7HlX7Nl_HY",
+ "fillStyle": "solid",
+ "strokeWidth": 4,
+ "strokeStyle": "dotted",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1799.471492174934,
+ "y": 2221.3874474682207,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 0.4985622223937298,
+ "height": 33.65304102189839,
+ "seed": 809602285,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1648718383497,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": null,
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 0.4985622223937298,
+ 33.65304102189839
+ ]
+ ]
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.png
new file mode 100644
index 00000000..40f740d5
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-distribution-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.excalidraw
new file mode 100644
index 00000000..735b8dbb
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.excalidraw
@@ -0,0 +1,1325 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "text",
+ "version": 928,
+ "versionNonce": 148875698,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1097.0634920634916,
+ "y": 1862.9047619047617,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 469,
+ "height": 35,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998586809,
+ "link": null,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "CCV - Consumer Initiated Slashing",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV - Consumer Initiated Slashing"
+ },
+ {
+ "type": "text",
+ "version": 1321,
+ "versionNonce": 1900012082,
+ "isDeleted": false,
+ "id": "yqi6dzvpzblq17ysowkTo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 827.4977622717156,
+ "y": 1970.1534947953671,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 138,
+ "height": 26,
+ "seed": 578772677,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490055,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1697,
+ "versionNonce": 323391662,
+ "isDeleted": false,
+ "id": "Y19fC1uuzabCFyV0s97Ip",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1774.6009368748905,
+ "y": 1920.5265106683828,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 148,
+ "height": 26,
+ "seed": 1161289003,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998572152,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chain"
+ },
+ {
+ "type": "text",
+ "version": 1378,
+ "versionNonce": 1663618034,
+ "isDeleted": false,
+ "id": "hE0ZFKm5e3BFhfSAr_icT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1019.6178858721332,
+ "y": 2020.711720440609,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 711940170,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490055,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 1593,
+ "versionNonce": 1502585902,
+ "isDeleted": false,
+ "id": "wTqYd1LVy1RtivTHEtctU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 440.04645730070445,
+ "y": 2014.7831490120375,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 147,
+ "height": 25,
+ "seed": 1503717526,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490055,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Staking Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Staking Module"
+ },
+ {
+ "type": "text",
+ "version": 1416,
+ "versionNonce": 380279090,
+ "isDeleted": false,
+ "id": "A_uPQosGByCb18jg5F8J3",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1608.6178858721328,
+ "y": 1957.1879109167996,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 1240462166,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998572152,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "rectangle",
+ "version": 3147,
+ "versionNonce": 1238376046,
+ "isDeleted": false,
+ "id": "34mJiRUfZv_whlGRmiJJO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1578.8152225891768,
+ "y": 1991.326747397683,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 560.3095238095233,
+ "height": 152.91269841269806,
+ "seed": 906912827,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646998579671,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2729,
+ "versionNonce": 1427746414,
+ "isDeleted": false,
+ "id": "XvKZ52HBgSZoisoCgiom8",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1604.4348764712604,
+ "y": 2092.2076997786344,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 271,
+ "height": 39,
+ "seed": 1134907637,
+ "groupIds": [
+ "PxynHKdbXOZO1gzKY2WrJ"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "b3SzkJI0k_8z9gcpTqmUE"
+ }
+ ],
+ "updated": 1646998652727,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2886,
+ "versionNonce": 1701918578,
+ "isDeleted": false,
+ "id": "b3SzkJI0k_8z9gcpTqmUE",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1609.4348764712604,
+ "y": 2097.2076997786344,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 261,
+ "height": 25,
+ "seed": 818685147,
+ "groupIds": [
+ "PxynHKdbXOZO1gzKY2WrJ"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998652727,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "XvKZ52HBgSZoisoCgiom8",
+ "originalText": "EndBlock"
+ },
+ {
+ "type": "text",
+ "version": 1743,
+ "versionNonce": 2033677490,
+ "isDeleted": false,
+ "id": "Oz5lUjmcWXSCVOUr-aDA6",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2082.892603541556,
+ "y": 1996.5985727945085,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 756640341,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998572152,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc2",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc2"
+ },
+ {
+ "type": "text",
+ "version": 1535,
+ "versionNonce": 1873602414,
+ "isDeleted": false,
+ "id": "m_vQSEBRga8RR7VuL27UY",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1921.2556987796524,
+ "y": 2014.4973823183186,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "transparent",
+ "width": 146,
+ "height": 25,
+ "seed": 765969819,
+ "groupIds": [
+ "lNYd5uxp6Xm5xLMU-zrby"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998572152,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "evidence(V, Hc1)",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "evidence(V, Hc1)"
+ },
+ {
+ "type": "arrow",
+ "version": 3778,
+ "versionNonce": 1535774322,
+ "isDeleted": false,
+ "id": "tvGd783eUc39eNWP1L7-j",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 2040.8207212669067,
+ "y": 2058.497674669792,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "#ced4da",
+ "width": 125.15697875770252,
+ "height": 1.0514920517930477,
+ "seed": 321384565,
+ "groupIds": [
+ "Uvbj_Wiv_4OirTlk_pHjj",
+ "lNYd5uxp6Xm5xLMU-zrby"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998572152,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -125.15697875770252,
+ -1.0514920517930477
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 3577,
+ "versionNonce": 487214514,
+ "isDeleted": false,
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1553.7271634489994,
+ "y": 2061.6224232101013,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "#ced4da",
+ "width": 124.1975520241399,
+ "height": 1.8686292115605738,
+ "seed": 564068565,
+ "groupIds": [
+ "kCy-i3wrt_ZLgIeQ4otUP"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998579670,
+ "link": null,
+ "startBinding": {
+ "elementId": "34mJiRUfZv_whlGRmiJJO",
+ "focus": 0.019438175195012165,
+ "gap": 25.088059140177393
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -124.1975520241399,
+ -1.8686292115605738
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1715,
+ "versionNonce": 404673582,
+ "isDeleted": false,
+ "id": "REPxg78R4E5rz3sm5KHSv",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1429.1842702082242,
+ "y": 2017.708121633686,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "transparent",
+ "width": 121,
+ "height": 25,
+ "seed": 208528123,
+ "groupIds": [
+ "kCy-i3wrt_ZLgIeQ4otUP"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "vpFHq3t2ppDoZ0A65BDZu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646998578518,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "SlashPacket",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "SlashPacket"
+ },
+ {
+ "type": "rectangle",
+ "version": 2926,
+ "versionNonce": 656230254,
+ "isDeleted": false,
+ "id": "XDYLAXX2W3oqW1A2aQPWE",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 362.7914130653671,
+ "y": 2049.49341406435,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1034.5,
+ "height": 188.57936507936463,
+ "seed": 521115163,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646998490056,
+ "link": null
+ },
+ {
+ "type": "arrow",
+ "version": 3877,
+ "versionNonce": 1506782834,
+ "isDeleted": false,
+ "id": "EVouO_KhDhhn4Dc-QBpIO",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 692.4977855419893,
+ "y": 2212.456760921986,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 272.89269154785916,
+ "height": 1.091620121523647,
+ "seed": 1274064149,
+ "groupIds": [
+ "YitB1O3hLStSm9d0ji7t9"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 272.89269154785916,
+ -1.091620121523647
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2701,
+ "versionNonce": 790505902,
+ "isDeleted": false,
+ "id": "qsmfvn2HY2RHLwEOsOUzi",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 714.8028231842457,
+ "y": 2168.99302141663,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 80,
+ "height": 25,
+ "seed": 713824955,
+ "groupIds": [
+ "YitB1O3hLStSm9d0ji7t9"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates"
+ },
+ {
+ "type": "rectangle",
+ "version": 2463,
+ "versionNonce": 2020937778,
+ "isDeleted": false,
+ "id": "qk20Z3SITwZB_fsRciLni",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1011.0420193284037,
+ "y": 2122.124366445302,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 311,
+ "height": 100.00000000000001,
+ "seed": 669270645,
+ "groupIds": [
+ "oDeq5vVUsHhsfwNK8oF5f"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "gOlzS2a5Jqr0i8pKHSLv7"
+ }
+ ],
+ "updated": 1646998490056,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2629,
+ "versionNonce": 1095287790,
+ "isDeleted": false,
+ "id": "gOlzS2a5Jqr0i8pKHSLv7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1016.0420193284037,
+ "y": 2134.624366445302,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 301,
+ "height": 75,
+ "seed": 1766787931,
+ "groupIds": [
+ "oDeq5vVUsHhsfwNK8oF5f"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- VSC := {updates}\n- provide VSC",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "qk20Z3SITwZB_fsRciLni",
+ "originalText": "EndBlock\n- VSC := {updates}\n- provide VSC"
+ },
+ {
+ "type": "rectangle",
+ "version": 2242,
+ "versionNonce": 1736980978,
+ "isDeleted": false,
+ "id": "Z8iTG7LRXktwbfyN2PngJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 433.6791974944824,
+ "y": 2153.112461683397,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 202,
+ "height": 73,
+ "seed": 1602104277,
+ "groupIds": [
+ "OjN-e-BfocNTU6MSi9MH2"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "ic_T2XDT20pmVr-i6K6mT"
+ }
+ ],
+ "updated": 1646998490056,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2299,
+ "versionNonce": 1604720174,
+ "isDeleted": false,
+ "id": "ic_T2XDT20pmVr-i6K6mT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 438.6791974944824,
+ "y": 2166.612461683397,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 192,
+ "height": 46,
+ "seed": 2141906939,
+ "groupIds": [
+ "OjN-e-BfocNTU6MSi9MH2"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- compute updates",
+ "baseline": 40,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "Z8iTG7LRXktwbfyN2PngJ",
+ "originalText": "EndBlock\n- compute updates"
+ },
+ {
+ "type": "text",
+ "version": 1569,
+ "versionNonce": 1712335794,
+ "isDeleted": false,
+ "id": "qWsxcIYo6PitI0T_lOAGZ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 370.01165116060577,
+ "y": 2056.5509537468897,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1389619509,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp"
+ },
+ {
+ "type": "arrow",
+ "version": 2743,
+ "versionNonce": 1662984302,
+ "isDeleted": false,
+ "id": "cDFbJ0gJjmHMqsUWmcRHo",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1439.909579382777,
+ "y": 2189.5700102244327,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 92.50326616584789,
+ "height": 1.2513122634102274,
+ "seed": 534734293,
+ "groupIds": [
+ "dE16wt7NaL6vKkhA161Rq"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 92.50326616584789,
+ -1.2513122634102274
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1351,
+ "versionNonce": 976479602,
+ "isDeleted": false,
+ "id": "xOTca9VBswKVU42Gp8Rf4",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1432.4699844939385,
+ "y": 2146.5676115550605,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 105,
+ "height": 25,
+ "seed": 1553092091,
+ "groupIds": [
+ "dE16wt7NaL6vKkhA161Rq"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket"
+ },
+ {
+ "type": "text",
+ "version": 143,
+ "versionNonce": 528278190,
+ "isDeleted": false,
+ "id": "Q4K_DBLw6Q2OCljBU7Wuz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1011.3985559225102,
+ "y": 2066.640239461175,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 382,
+ "height": 25,
+ "seed": 361141403,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "request Slash(V, VSCtoH[HtoVSC[Hc1]])",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "request Slash(V, VSCtoH[HtoVSC[Hc1]])"
+ },
+ {
+ "type": "arrow",
+ "version": 3915,
+ "versionNonce": 1295673138,
+ "isDeleted": false,
+ "id": "7x2vU_cDTbkEtKs9PHzx4",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 968.2018376263626,
+ "y": 2079.364743828038,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 96.46370626484804,
+ "height": 0.26527698055974724,
+ "seed": 451933083,
+ "groupIds": [
+ "8RY6Q6u1gjiJW9pVBSA-G"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -96.46370626484804,
+ 0.26527698055974724
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 152,
+ "versionNonce": 124280046,
+ "isDeleted": false,
+ "id": "G-kvSnhuwz_o8zVNMnRfw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 743.755698779653,
+ "y": 2070.640239461175,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "transparent",
+ "width": 78,
+ "height": 25,
+ "seed": 124916443,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Slash(V)",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Slash(V)"
+ },
+ {
+ "type": "text",
+ "version": 1552,
+ "versionNonce": 1200271602,
+ "isDeleted": false,
+ "id": "C8kWqIDQW5MX6a6ah4kSq",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 705.7080797320335,
+ "y": 2014.9887705242381,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 152,
+ "height": 25,
+ "seed": 1595117284,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Slashing Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Slashing Module"
+ },
+ {
+ "type": "text",
+ "version": 183,
+ "versionNonce": 1349160750,
+ "isDeleted": false,
+ "id": "RR6VdNWjbLRrErohTTbBm",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 746.8330797320335,
+ "y": 2103.863770524238,
+ "strokeColor": "#c92a2a",
+ "backgroundColor": "transparent",
+ "width": 62,
+ "height": 25,
+ "seed": 1521074404,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Jail(V)",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Jail(V)"
+ },
+ {
+ "type": "arrow",
+ "version": 4054,
+ "versionNonce": 749454002,
+ "isDeleted": false,
+ "id": "klgx4MBotg1iyiCI49QGG",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 711.5649328644578,
+ "y": 2099.981132033958,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 58.481294157559205,
+ "height": 0.48010792003151437,
+ "seed": 1400894308,
+ "groupIds": [
+ "9GTuKCxQp5kiE4U_bEtRe"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -58.481294157559205,
+ 0.48010792003151437
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 281,
+ "versionNonce": 384596334,
+ "isDeleted": false,
+ "id": "aafBj4XUKtz6lcnF74kwy",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 438.0830797320335,
+ "y": 2107.613770524238,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 168,
+ "height": 25,
+ "seed": 1959541340,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646998490056,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "update V's power",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "update V's power"
+ },
+ {
+ "id": "WXzZGFSNUEGRrF93YWfRx",
+ "type": "text",
+ "x": 1605.0464573007043,
+ "y": 2040.7831490120375,
+ "width": 293,
+ "height": 25,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 948907054,
+ "version": 184,
+ "versionNonce": 79284654,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1646998572152,
+ "link": null,
+ "text": "request Slash(V, HtoVSC[Hc1])",
+ "fontSize": 20,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 18,
+ "containerId": null,
+ "originalText": "request Slash(V, HtoVSC[Hc1])"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.png
new file mode 100644
index 00000000..8534d567
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-evidence-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.excalidraw
new file mode 100644
index 00000000..940adcaf
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.excalidraw
@@ -0,0 +1,1661 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 2837,
+ "versionNonce": 965340827,
+ "isDeleted": false,
+ "id": "62Yt6MeweG9N-ggINnoIf",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 907.6511117600413,
+ "y": 5030.324182124006,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 390.16666666666634,
+ "height": 168.34126984126968,
+ "seed": 2045471701,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103271356,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2287,
+ "versionNonce": 454151494,
+ "isDeleted": false,
+ "id": "-1fHuzFIcWdo4pLFPrcu7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 984.4731465945056,
+ "y": 5073.193229743054,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 300,
+ "height": 110,
+ "seed": 2108794875,
+ "groupIds": [
+ "99ydYVD5vvH65vuufG0Wv"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "W2uPzsED7tDrwRJZQrV1T"
+ }
+ ],
+ "updated": 1645816545687,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2435,
+ "versionNonce": 1263719322,
+ "isDeleted": false,
+ "id": "W2uPzsED7tDrwRJZQrV1T",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 989.4731465945056,
+ "y": 5090.693229743054,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 290,
+ "height": 75,
+ "seed": 765012277,
+ "groupIds": [
+ "99ydYVD5vvH65vuufG0Wv"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816549808,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- provide VSC1\n- VSCtoH[VSC1.id] := Hp1 + 1 ",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "-1fHuzFIcWdo4pLFPrcu7",
+ "originalText": "EndBlock\n- provide VSC1\n- VSCtoH[VSC1.id] := Hp1 + 1 "
+ },
+ {
+ "type": "text",
+ "version": 1384,
+ "versionNonce": 348557877,
+ "isDeleted": false,
+ "id": "E6CeNc4P-6BSf8ozSW8TU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 918.7403974743271,
+ "y": 5040.417436092261,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1926295707,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103204661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp1",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp1"
+ },
+ {
+ "type": "arrow",
+ "version": 2464,
+ "versionNonce": 2057876987,
+ "isDeleted": false,
+ "id": "cXxMrIS5bJqBIcnvcsjjy",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1334.6677848860563,
+ "y": 5183.186288924198,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 92.50326616584789,
+ "height": 1.2513122634102274,
+ "seed": 325597845,
+ "groupIds": [
+ "I1Ed_dtSoESPK0KEygHw1"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1644948503913,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 92.50326616584789,
+ -1.2513122634102274
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1062,
+ "versionNonce": 1776417589,
+ "isDeleted": false,
+ "id": "CBELU9TME1J6PtfJ3TJHw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1320.2281899972177,
+ "y": 5140.183890254825,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 119,
+ "height": 25,
+ "seed": 1648239931,
+ "groupIds": [
+ "I1Ed_dtSoESPK0KEygHw1"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1644948503913,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-1"
+ },
+ {
+ "type": "rectangle",
+ "version": 3059,
+ "versionNonce": 1554198261,
+ "isDeleted": false,
+ "id": "No1W5o5O4PpPljGn9CEwz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1465.6686661876934,
+ "y": 5169.846588667523,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 372.119047619048,
+ "height": 184.0555555555555,
+ "seed": 68034549,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103152506,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2414,
+ "versionNonce": 1123039765,
+ "isDeleted": false,
+ "id": "I-j-k0NQxt5NOw6eGqehh",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1479.0978438793,
+ "y": 5235.9180172389515,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 285.00000000000006,
+ "height": 110,
+ "seed": 593216987,
+ "groupIds": [
+ "T1BKmx5mYFD-4DOophhvO"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "cAQkbVPZOpkElVNE8Kqn2"
+ }
+ ],
+ "updated": 1645103123299,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2538,
+ "versionNonce": 735075418,
+ "isDeleted": false,
+ "id": "cAQkbVPZOpkElVNE8Kqn2",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1484.0978438793,
+ "y": 5253.4180172389515,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 275,
+ "height": 75,
+ "seed": 630217045,
+ "groupIds": [
+ "T1BKmx5mYFD-4DOophhvO"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816576806,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- apply VSC1\n- HtoVSC[Hc1 + 1] := VSC1.id",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "I-j-k0NQxt5NOw6eGqehh",
+ "originalText": "EndBlock\n- apply VSC1\n- HtoVSC[Hc1 + 1] := VSC1.id"
+ },
+ {
+ "type": "text",
+ "version": 1632,
+ "versionNonce": 1007559509,
+ "isDeleted": false,
+ "id": "3oZfSAsXDf5sWpf01s4pI",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1782.98414237817,
+ "y": 5172.166033111967,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 2101105275,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103156577,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc1",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc1"
+ },
+ {
+ "type": "text",
+ "version": 2421,
+ "versionNonce": 1898682427,
+ "isDeleted": false,
+ "id": "19WoaZ7J-S1If0NfPs9PH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1484.3948566638837,
+ "y": 5188.541033111967,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 118,
+ "height": 25,
+ "seed": 1095001781,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1644948639110,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "receive VSC1",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "receive VSC1"
+ },
+ {
+ "type": "text",
+ "version": 984,
+ "versionNonce": 911500597,
+ "isDeleted": false,
+ "id": "i-n6SyZpNPXPUJu9taI5s",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1089.7313175452955,
+ "y": 4947.662064857999,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 616,
+ "height": 35,
+ "seed": 815942427,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103108298,
+ "link": null,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "Mapping consumer heights to provider heights",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Mapping consumer heights to provider heights"
+ },
+ {
+ "type": "text",
+ "version": 1441,
+ "versionNonce": 1850382235,
+ "isDeleted": false,
+ "id": "U5v5aBN1bBklzL2e_E3Qh",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 913.3821111960893,
+ "y": 4999.713652159587,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 201,
+ "height": 25,
+ "seed": 1506789397,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103204661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 1796,
+ "versionNonce": 849408123,
+ "isDeleted": false,
+ "id": "cxrPxyXpJAc0rH5HBCRkX",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1470.866238180216,
+ "y": 5138.110477556412,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 210,
+ "height": 25,
+ "seed": 1107308475,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645025980238,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer CCV Module"
+ },
+ {
+ "type": "rectangle",
+ "version": 3211,
+ "versionNonce": 154302235,
+ "isDeleted": false,
+ "id": "kUudVyZSNvGqQYifXHGGh",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1466.8146508786285,
+ "y": 5385.3128585087925,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 373.11904761904765,
+ "height": 129.7698412698417,
+ "seed": 19398005,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103148937,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2509,
+ "versionNonce": 1493429,
+ "isDeleted": false,
+ "id": "rXsCSaTB72BKPYUmggGQY",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1479.2438285702356,
+ "y": 5414.241429937365,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 290.0000000000001,
+ "height": 85,
+ "seed": 58228827,
+ "groupIds": [
+ "51DYqIRaI9heMJM5SgRPU"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "rpdi-BunExE9XZbf1DVUP"
+ }
+ ],
+ "updated": 1645103136093,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2635,
+ "versionNonce": 255941446,
+ "isDeleted": false,
+ "id": "rpdi-BunExE9XZbf1DVUP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1484.2438285702356,
+ "y": 5431.741429937365,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 280,
+ "height": 75,
+ "seed": 1855096533,
+ "groupIds": [
+ "51DYqIRaI9heMJM5SgRPU"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816584697,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- HtoVSC[Hc2 + 1] := VSC1.id",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "rXsCSaTB72BKPYUmggGQY",
+ "originalText": "EndBlock\n- HtoVSC[Hc2 + 1] := VSC1.id"
+ },
+ {
+ "type": "text",
+ "version": 1750,
+ "versionNonce": 1257390005,
+ "isDeleted": false,
+ "id": "Fe-cxg4fKpsT136FN6Qzn",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1785.1301270691047,
+ "y": 5389.489445810381,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1591590139,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103142648,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc2",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc2"
+ },
+ {
+ "type": "rectangle",
+ "version": 2863,
+ "versionNonce": 1773168277,
+ "isDeleted": false,
+ "id": "Tr2B0fvNTq7R29rb6VFGS",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 905.5856355695652,
+ "y": 5314.027144223079,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 393.83333333333303,
+ "height": 168.34126984126968,
+ "seed": 1420092469,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103260214,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2371,
+ "versionNonce": 2094327803,
+ "isDeleted": false,
+ "id": "PMQGUy1YOdyiI8lgmIEiG",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 982.407670404029,
+ "y": 5357.896191842126,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 303.33333333333337,
+ "height": 110,
+ "seed": 1605526939,
+ "groupIds": [
+ "N6hgWfxNSZpIjH1COoQX1"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "SNoe2XrPwmu5wOZWdd3nR"
+ }
+ ],
+ "updated": 1645103248439,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2520,
+ "versionNonce": 977917978,
+ "isDeleted": false,
+ "id": "SNoe2XrPwmu5wOZWdd3nR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 987.407670404029,
+ "y": 5375.396191842126,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 293,
+ "height": 100,
+ "seed": 2000983445,
+ "groupIds": [
+ "N6hgWfxNSZpIjH1COoQX1"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816558928,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- provide VSC2\n- VSCtoH[VSC2.id] := Hp2 + 1\n",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "PMQGUy1YOdyiI8lgmIEiG",
+ "originalText": "EndBlock\n- provide VSC2\n- VSCtoH[VSC2.id] := Hp2 + 1 "
+ },
+ {
+ "type": "text",
+ "version": 1446,
+ "versionNonce": 198683867,
+ "isDeleted": false,
+ "id": "ScFnW2Jx_OZLAciQ4_FIW",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 916.6749212838505,
+ "y": 5324.120398191333,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 2099837499,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103204661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp2",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp2"
+ },
+ {
+ "type": "arrow",
+ "version": 2525,
+ "versionNonce": 1865653877,
+ "isDeleted": false,
+ "id": "lw6S8rWrOfXpLFOCnJpvh",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1332.6023086955797,
+ "y": 5468.317822451842,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 92.50326616584789,
+ "height": 1.2513122634102274,
+ "seed": 27728629,
+ "groupIds": [
+ "pIcEEnE-tG25_rcW4Tqbs"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1644948503914,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 92.50326616584789,
+ -1.2513122634102274
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1125,
+ "versionNonce": 1438487707,
+ "isDeleted": false,
+ "id": "gBLwsoJXj_hprEaAnsQ1P",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1314.162713806741,
+ "y": 5425.315423782469,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 25,
+ "seed": 606337755,
+ "groupIds": [
+ "pIcEEnE-tG25_rcW4Tqbs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1644948503914,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-2"
+ },
+ {
+ "type": "rectangle",
+ "version": 2936,
+ "versionNonce": 1648125013,
+ "isDeleted": false,
+ "id": "gQmWRjeS-ocYAYaflLEgA",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 908.8713498552792,
+ "y": 5529.3128585087925,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 390.83333333333314,
+ "height": 168.34126984126968,
+ "seed": 475133013,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103299246,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2389,
+ "versionNonce": 283315654,
+ "isDeleted": false,
+ "id": "dokEAn1FQ_oVlBA8J7ItM",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 981.6933846897432,
+ "y": 5572.18190612784,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 302.44444444444457,
+ "height": 110,
+ "seed": 20309883,
+ "groupIds": [
+ "n6z4skjXjB172Ld2SykcH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "JkqonPURMZvYfLdV_1F7j"
+ }
+ ],
+ "updated": 1645816561041,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2539,
+ "versionNonce": 1112767642,
+ "isDeleted": false,
+ "id": "JkqonPURMZvYfLdV_1F7j",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 986.6933846897432,
+ "y": 5589.68190612784,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 292,
+ "height": 100,
+ "seed": 1125818805,
+ "groupIds": [
+ "n6z4skjXjB172Ld2SykcH"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816565864,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- provide VSC3\n- VSCtoH[VSC3.id] := Hp3 + 1\n",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "dokEAn1FQ_oVlBA8J7ItM",
+ "originalText": "EndBlock\n- provide VSC3\n- VSCtoH[VSC3.id] := Hp3 + 1 "
+ },
+ {
+ "type": "text",
+ "version": 1526,
+ "versionNonce": 1519415835,
+ "isDeleted": false,
+ "id": "4YehNfHqd3bVc0R21JU7D",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 918.9606355695645,
+ "y": 5538.406112477047,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 178339867,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103204661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHp3",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHp3"
+ },
+ {
+ "type": "arrow",
+ "version": 2603,
+ "versionNonce": 1398862779,
+ "isDeleted": false,
+ "id": "vDGT6MZoRqrttkMOjZWFy",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1334.888022981294,
+ "y": 5682.603536737557,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 92.50326616584789,
+ "height": 1.2513122634102274,
+ "seed": 1529258773,
+ "groupIds": [
+ "be6eOt5A0GPdwHwuPmwqF"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1644948503914,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 92.50326616584789,
+ -1.2513122634102274
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1205,
+ "versionNonce": 41456117,
+ "isDeleted": false,
+ "id": "lmLsw4wlozylYSiHWv8Bj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1316.4484280924553,
+ "y": 5639.601138068183,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 25,
+ "seed": 1247315131,
+ "groupIds": [
+ "be6eOt5A0GPdwHwuPmwqF"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1644948503914,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-3",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-3"
+ },
+ {
+ "type": "rectangle",
+ "version": 3227,
+ "versionNonce": 1617677147,
+ "isDeleted": false,
+ "id": "cRlMLHw5bynb5zCrfYJ7P",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1466.9575080214863,
+ "y": 5636.455715651651,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 374.1190476190478,
+ "height": 230.48412698412702,
+ "seed": 149649525,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645103174042,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2565,
+ "versionNonce": 1697772853,
+ "isDeleted": false,
+ "id": "wui2IuqDrnmdzga3MFffG",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1483.243828570236,
+ "y": 5746.812858508794,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 315,
+ "height": 103.57142857142843,
+ "seed": 1744713051,
+ "groupIds": [
+ "b9iSJpY4i7SIaYvj3lPVr"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "1bmuay5KcLdBGV2XpFahr"
+ }
+ ],
+ "updated": 1645025812420,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2705,
+ "versionNonce": 1085376282,
+ "isDeleted": false,
+ "id": "1bmuay5KcLdBGV2XpFahr",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1488.243828570236,
+ "y": 5761.098572794508,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 305,
+ "height": 75,
+ "seed": 1177461205,
+ "groupIds": [
+ "b9iSJpY4i7SIaYvj3lPVr"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645816592497,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- apply aggregate(VSC2,VSC3)\n- HtoVSC[Hc3 + 1] := VSC3.id",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "wui2IuqDrnmdzga3MFffG",
+ "originalText": "EndBlock\n- apply aggregate(VSC2,VSC3)\n- HtoVSC[Hc3 + 1] := VSC3.id"
+ },
+ {
+ "type": "text",
+ "version": 1686,
+ "versionNonce": 1345476949,
+ "isDeleted": false,
+ "id": "UhygML5dIR0UtW1cF-vNV",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1784.2729842119625,
+ "y": 5641.7751600960955,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 50,
+ "seed": 1927598587,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645103177428,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block\nHc3",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block\nHc3"
+ },
+ {
+ "type": "text",
+ "version": 2482,
+ "versionNonce": 1963563323,
+ "isDeleted": false,
+ "id": "5mV4y7gVFN-4cXn883mq0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1485.6836984976762,
+ "y": 5655.1501600960955,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 25,
+ "seed": 939892533,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645025812420,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "receive VSC2",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "receive VSC2"
+ },
+ {
+ "type": "text",
+ "version": 2511,
+ "versionNonce": 1826876405,
+ "isDeleted": false,
+ "id": "juYb5ZBjHocY6Dbqx0CDO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1488.0170318310097,
+ "y": 5698.840636286572,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 126,
+ "height": 25,
+ "seed": 1812912795,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645025812420,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "receive VSC3",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "receive VSC3"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.png
new file mode 100644
index 00000000..bfbc0987
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-height-mapping-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.excalidraw
new file mode 100644
index 00000000..7a882f41
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.excalidraw
@@ -0,0 +1,2987 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 2236,
+ "versionNonce": 952517908,
+ "isDeleted": false,
+ "id": "VyRoLp37OULOh8PtwSL1o",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 319.55701296942993,
+ "y": 890.1733360652088,
+ "strokeColor": "#ced4da",
+ "backgroundColor": "#ced4da",
+ "width": 1247.666666666667,
+ "height": 341.19444444444423,
+ "seed": 1900272787,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834907847,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1755,
+ "versionNonce": 2027932204,
+ "isDeleted": false,
+ "id": "bV3e1QXlGeho4mNT0Md4J",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 321.918124080541,
+ "y": 756.5622249540979,
+ "strokeColor": "#ced4da",
+ "backgroundColor": "#ced4da",
+ "width": 1245.5555555555554,
+ "height": 112.99999999999996,
+ "seed": 524846941,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1610,
+ "versionNonce": 1568292500,
+ "isDeleted": false,
+ "id": "cIdDFggUL2V75qBzn7_1U",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 325.7236796360967,
+ "y": 517.3677805096532,
+ "strokeColor": "#ced4da",
+ "backgroundColor": "#ced4da",
+ "width": 1241.11111111111,
+ "height": 218.00000000000003,
+ "seed": 602414589,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1422,
+ "versionNonce": 2101191852,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 569.8333333333333,
+ "y": 251.05555555555554,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 534,
+ "height": 35,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "CCV - Channel Initialization: New Chain",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV - Channel Initialization: New Chain"
+ },
+ {
+ "type": "text",
+ "version": 562,
+ "versionNonce": 979192852,
+ "isDeleted": false,
+ "id": "ghfMBQkamou-QJWwVrzMO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 355.88888888888886,
+ "y": 400.5,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 138,
+ "height": 26,
+ "seed": 1747022505,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1134,
+ "versionNonce": 503066412,
+ "isDeleted": false,
+ "id": "ONYRFM5fo0s39BeH4ty3j",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1115.333333333333,
+ "y": 481.8888888888889,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 148,
+ "height": 26,
+ "seed": 129325673,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chain"
+ },
+ {
+ "type": "diamond",
+ "version": 1110,
+ "versionNonce": 1921165716,
+ "isDeleted": false,
+ "id": "D9UpDlJvj-vqduCyp6YiL",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 888.5595382219559,
+ "y": 780.4511138429862,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 84.27272727272718,
+ "height": 71.99999999999996,
+ "seed": 1864416436,
+ "groupIds": [
+ "HpIe0q_ivXIeva3ZeZcXq"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1032,
+ "versionNonce": 1803905452,
+ "isDeleted": false,
+ "id": "olDFMzwulEulDGtmwqPUT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 901.2413564037735,
+ "y": 805.8147502066226,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 58.90909090909091,
+ "height": 21.272727272727266,
+ "seed": 856808204,
+ "groupIds": [
+ "HpIe0q_ivXIeva3ZeZcXq"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false,
+ "fontSize": 16.363636363636367,
+ "fontFamily": 1,
+ "text": "Relayer",
+ "baseline": 14.272727272727266,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": null,
+ "originalText": "Relayer"
+ },
+ {
+ "type": "arrow",
+ "version": 4261,
+ "versionNonce": 153114388,
+ "isDeleted": false,
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 975.5518074865438,
+ "y": 817.6513836813763,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 122.34783859303411,
+ "height": 0.13529418971859286,
+ "seed": 671433780,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876872,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "olDFMzwulEulDGtmwqPUT",
+ "focus": 0.10785213981894373,
+ "gap": 15.401360173679507
+ },
+ "endBinding": {
+ "elementId": "Li4WakMROOrh4VO9QnPFJ",
+ "focus": 0.20600273305136005,
+ "gap": 9.546255778740829
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 122.34783859303411,
+ 0.13529418971859286
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 4765,
+ "versionNonce": 1493390380,
+ "isDeleted": false,
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 888.5533491011068,
+ "y": 820.2579234494533,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 127.11210337472005,
+ "height": 1.3074457047832766,
+ "seed": 1573053876,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "olDFMzwulEulDGtmwqPUT",
+ "focus": -0.3876178524442418,
+ "gap": 12.688007302666563
+ },
+ "endBinding": {
+ "elementId": "8j6p54tqGlb2RxbOlwOCj",
+ "focus": -0.07876390764322155,
+ "gap": 6.662010534734691
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -127.11210337472005,
+ -1.3074457047832766
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2191,
+ "versionNonce": 715936916,
+ "isDeleted": false,
+ "id": "BfihjIK4S9ZseoXjeT71N",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 829.6959018583196,
+ "y": 762.4511138429866,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 206,
+ "height": 26,
+ "seed": 1202279692,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "connection handshake",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "connection handshake"
+ },
+ {
+ "type": "rectangle",
+ "version": 940,
+ "versionNonce": 1684008620,
+ "isDeleted": false,
+ "id": "8j6p54tqGlb2RxbOlwOCj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 346.668124080541,
+ "y": 435.978891620764,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 408.1111111111111,
+ "height": 827.0833333333335,
+ "seed": 38506771,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1140,
+ "versionNonce": 81568276,
+ "isDeleted": false,
+ "id": "Li4WakMROOrh4VO9QnPFJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1107.4459018583188,
+ "y": 519.0900027318751,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 382.1111111111107,
+ "height": 753.055555555556,
+ "seed": 1423042963,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 2693,
+ "versionNonce": 144464300,
+ "isDeleted": false,
+ "id": "cN8JVC0YU7ylsJMNyoLMx",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1283.4459018583188,
+ "y": 554.2011138429866,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 193.00000000000009,
+ "height": 690.2777777777778,
+ "seed": 1979822931,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "dXiPD3WzFtcIXoXAUQVpo",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "dyJw9z0fJ3VE34NAIchq_",
+ "type": "arrow"
+ },
+ {
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834895267,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1023,
+ "versionNonce": 514277268,
+ "isDeleted": false,
+ "id": "Iw9eosLogOHYo9M8Jsbr3",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1317.4459018583188,
+ "y": 528.2011138429866,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 378901469,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 2114,
+ "versionNonce": 778973484,
+ "isDeleted": false,
+ "id": "QU6RqfKS-_y9s-kS2v306",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1290.0986796360967,
+ "y": 576.3122249540977,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 65,
+ "height": 26,
+ "seed": 1163898717,
+ "groupIds": [
+ "ajix7t8vF1k67Cs7w6vm6"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927992,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "create",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "create"
+ },
+ {
+ "type": "text",
+ "version": 2234,
+ "versionNonce": 1537853332,
+ "isDeleted": false,
+ "id": "ClT976yNQ_fMatiXU52On",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1363.5986796360967,
+ "y": 576.3122249540977,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 87769331,
+ "groupIds": [
+ "ajix7t8vF1k67Cs7w6vm6"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927992,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "arrow",
+ "version": 5900,
+ "versionNonce": 236052012,
+ "isDeleted": false,
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 572.8247330182278,
+ "y": 564.5851565198477,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 525.0477037424328,
+ "height": 10.736116733831409,
+ "seed": 1757145171,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": -0.7838529906461132,
+ "gap": 13.045497826575456
+ },
+ "endBinding": {
+ "elementId": "Li4WakMROOrh4VO9QnPFJ",
+ "focus": 0.8311393955556673,
+ "gap": 9.573465097658186
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 525.0477037424328,
+ 10.736116733831409
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1819,
+ "versionNonce": 2065918612,
+ "isDeleted": false,
+ "id": "obb5_vEy0bQhgCVmGyg2U",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 822.9165850810258,
+ "y": 541.8677805096532,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 217,
+ "height": 26,
+ "seed": 1080124915,
+ "groupIds": [
+ "3L8N_qI1cIkTh8PTodA99"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "start consumer chain ",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "start consumer chain "
+ },
+ {
+ "type": "text",
+ "version": 1857,
+ "versionNonce": 1259157676,
+ "isDeleted": false,
+ "id": "Y8isoLMncxer7kjVuxmbv",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 776.4165850810263,
+ "y": 572.8677805096532,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 132,
+ "height": 26,
+ "seed": 2101765821,
+ "groupIds": [
+ "3L8N_qI1cIkTh8PTodA99"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "validator set",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "validator set"
+ },
+ {
+ "type": "text",
+ "version": 1858,
+ "versionNonce": 570488852,
+ "isDeleted": false,
+ "id": "gEd9zB-Zfifc6sNT9rzro",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 942.3054739699143,
+ "y": 572.7566693985422,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 132,
+ "height": 26,
+ "seed": 767963027,
+ "groupIds": [
+ "3L8N_qI1cIkTh8PTodA99"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "validator set",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "validator set"
+ },
+ {
+ "type": "text",
+ "version": 1849,
+ "versionNonce": 884556588,
+ "isDeleted": false,
+ "id": "brcIV75-78ydMBBS09vp6",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 916.4165850810258,
+ "y": 568.8677805096532,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 17,
+ "height": 36,
+ "seed": 480544541,
+ "groupIds": [
+ "3L8N_qI1cIkTh8PTodA99"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "=",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "="
+ },
+ {
+ "type": "rectangle",
+ "version": 2320,
+ "versionNonce": 13686164,
+ "isDeleted": false,
+ "id": "fatyAFozygc0HN-v5nRU0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 362.6959018583192,
+ "y": 482.0622249540977,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 197.08333333333317,
+ "height": 757.0833333333335,
+ "seed": 135825021,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 4164,
+ "versionNonce": 1367342508,
+ "isDeleted": false,
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 291.87712540922803,
+ "y": 558.1545895841169,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 63.7971337489638,
+ "height": 1.9791038206386702,
+ "seed": 679224755,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "OqQOU1nnCT4lg6pSCaQ7a",
+ "focus": 0.0805401488072919,
+ "gap": 1.9867791064644393
+ },
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": 0.8063529720210383,
+ "gap": 7.021642700127359
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 63.7971337489638,
+ -1.9791038206386702
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1671,
+ "versionNonce": 80282388,
+ "isDeleted": false,
+ "id": "U0Bl8vDZOyZGGb-5ZZTLb",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 373.97367963609713,
+ "y": 547.1733360652089,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 65,
+ "height": 26,
+ "seed": 1174218099,
+ "groupIds": [
+ "WKHwDuPMLSCmZygHZrpto"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "create",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "create"
+ },
+ {
+ "type": "text",
+ "version": 1749,
+ "versionNonce": 1250650156,
+ "isDeleted": false,
+ "id": "OuxN9IY6BSSbtbdmqHSto",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 446.47367963609713,
+ "y": 547.1733360652089,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 2145935165,
+ "groupIds": [
+ "WKHwDuPMLSCmZygHZrpto"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "text",
+ "version": 977,
+ "versionNonce": 73367700,
+ "isDeleted": false,
+ "id": "DqEozYcKYxDC28R2NnxW0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 411.5570129694301,
+ "y": 446.2011138429867,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 279128861,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 2108,
+ "versionNonce": 331711148,
+ "isDeleted": false,
+ "id": "T1izXFIsp7tDrPbX0eIxf",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1499.9736796360964,
+ "y": 566.3122249540976,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 59,
+ "height": 26,
+ "seed": 586769011,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ICS-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ICS-2"
+ },
+ {
+ "type": "text",
+ "version": 2313,
+ "versionNonce": 390745620,
+ "isDeleted": false,
+ "id": "t_J1VfmHv9u7EE-WzNnYj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1497.4736796360964,
+ "y": 801.4511138429867,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 59,
+ "height": 26,
+ "seed": 561448477,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ICS-3",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ICS-3"
+ },
+ {
+ "type": "text",
+ "version": 2572,
+ "versionNonce": 397401364,
+ "isDeleted": false,
+ "id": "6aTTvXXhRp_Om_uUJ18Yj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 857.9934333771513,
+ "y": 931.9788916207647,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 26,
+ "seed": 180182749,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834948174,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "channel handshake",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "channel handshake"
+ },
+ {
+ "type": "text",
+ "version": 2013,
+ "versionNonce": 1853844372,
+ "isDeleted": false,
+ "id": "ZUY3S70EAhRzG6opdy45g",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1111.7652089590333,
+ "y": 894.8373046083263,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 151,
+ "height": 26,
+ "seed": 1975458397,
+ "groupIds": [
+ "MKCuh3Ezvdojoiw9C9BJi"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenInit",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenInit"
+ },
+ {
+ "type": "arrow",
+ "version": 5041,
+ "versionNonce": 1278068652,
+ "isDeleted": false,
+ "id": "dyJw9z0fJ3VE34NAIchq_",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1115.0775853573923,
+ "y": 937.8690346047606,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 159.7789284780024,
+ "height": 0.8821154837639824,
+ "seed": 488375795,
+ "groupIds": [
+ "MKCuh3Ezvdojoiw9C9BJi"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "HQSTyus42WtiUMx7P8B2w",
+ "focus": 0.06333826616831273,
+ "gap": 17.256054689591338
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 159.7789284780024,
+ -0.8821154837639824
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2071,
+ "versionNonce": 505373076,
+ "isDeleted": false,
+ "id": "OLVnAm75K0c9kLnrUS8a3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1115.381446312339,
+ "y": 1021.9352488447694,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 148,
+ "height": 26,
+ "seed": 1042999091,
+ "groupIds": [
+ "zm2GFhRP96ueQ_yNG_kXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834959340,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenAck",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenAck"
+ },
+ {
+ "type": "arrow",
+ "version": 4977,
+ "versionNonce": 275123988,
+ "isDeleted": false,
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1117.193822710698,
+ "y": 1064.9669788412034,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 151.68843185701462,
+ "height": 0.10234261104551479,
+ "seed": 1002477949,
+ "groupIds": [
+ "zm2GFhRP96ueQ_yNG_kXo"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834961002,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "cN8JVC0YU7ylsJMNyoLMx",
+ "focus": -0.48030803212548806,
+ "gap": 14.56364729060624
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 151.68843185701462,
+ 0.10234261104551479
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1684,
+ "versionNonce": 1004574356,
+ "isDeleted": false,
+ "id": "1i9SM2FmlaLGLKGuMadr3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 562.7022437933846,
+ "y": 1141.0635754416971,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 184,
+ "height": 26,
+ "seed": 23310525,
+ "groupIds": [
+ "CNZ2bx0Kmyw6NZbWgff33"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenConfirm",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenConfirm"
+ },
+ {
+ "type": "arrow",
+ "version": 4378,
+ "versionNonce": 1627966636,
+ "isDeleted": false,
+ "id": "jz87Jfblhql4prXUNauec",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 745.4327595896756,
+ "y": 1185.08156341232,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 178.3329820370053,
+ "height": 1.7496207741528451,
+ "seed": 2088404371,
+ "groupIds": [
+ "CNZ2bx0Kmyw6NZbWgff33"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": 0.8476476180141543,
+ "gap": 7.320542361017942
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -178.3329820370053,
+ -1.7496207741528451
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1579,
+ "versionNonce": 708355092,
+ "isDeleted": false,
+ "id": "tFHjTvU-Pd7PxTc9LTr4w",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 591.945901858319,
+ "y": 921.7754531910084,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 149,
+ "height": 26,
+ "seed": 19313843,
+ "groupIds": [
+ "vXfReGIEP1cvs4DENwZ3s"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenTry",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenTry"
+ },
+ {
+ "type": "arrow",
+ "version": 4289,
+ "versionNonce": 774002476,
+ "isDeleted": false,
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 748.0653065434987,
+ "y": 965.7934411616313,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 177.7865820739139,
+ "height": 2.586240593854086,
+ "seed": 290612221,
+ "groupIds": [
+ "vXfReGIEP1cvs4DENwZ3s"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": 0.2658518655133844,
+ "gap": 10.499489277932412
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -177.7865820739139,
+ -2.586240593854086
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1137,
+ "versionNonce": 2100494740,
+ "isDeleted": false,
+ "id": "NMo9QrIPRXbhU3R-JdTPp",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1117.4966157758395,
+ "y": 532.240804400325,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 109.23098275619242,
+ "height": 25.818232287827296,
+ "seed": 1048478237,
+ "groupIds": [
+ "qkwovs2kx-e3xwC2tuyLC"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 19.860178682944078,
+ "fontFamily": 1,
+ "text": "InitGenesis",
+ "baseline": 17.818232287827296,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "InitGenesis"
+ },
+ {
+ "type": "arrow",
+ "version": 4712,
+ "versionNonce": 1688637868,
+ "isDeleted": false,
+ "id": "dXiPD3WzFtcIXoXAUQVpo",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1111.360489377364,
+ "y": 574.978794786868,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 159.2604262870193,
+ "height": 1.128331266536179,
+ "seed": 1345845811,
+ "groupIds": [
+ "qkwovs2kx-e3xwC2tuyLC"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "cN8JVC0YU7ylsJMNyoLMx",
+ "gap": 12.824986193935452,
+ "focus": 0.9324386355587645
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 159.2604262870193,
+ 1.128331266536179
+ ]
+ ]
+ },
+ {
+ "type": "diamond",
+ "version": 1568,
+ "versionNonce": 1637297708,
+ "isDeleted": false,
+ "id": "ufedJ6N2PtgoNmILgLeR9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 886.5039826664,
+ "y": 958.3122249540979,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 84.27272727272718,
+ "height": 71.99999999999996,
+ "seed": 1428807773,
+ "groupIds": [
+ "IcSLxZ0ZPqcKuPbEB_1Pa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834948174,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1489,
+ "versionNonce": 560498860,
+ "isDeleted": false,
+ "id": "s7XdvhPQZXbsLet4haeHa",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 899.1858008482176,
+ "y": 983.6758613177344,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 58.90909090909091,
+ "height": 21.272727272727266,
+ "seed": 1395171315,
+ "groupIds": [
+ "IcSLxZ0ZPqcKuPbEB_1Pa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834948174,
+ "link": null,
+ "locked": false,
+ "fontSize": 16.363636363636367,
+ "fontFamily": 1,
+ "text": "Relayer",
+ "baseline": 14.272727272727266,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": null,
+ "originalText": "Relayer"
+ },
+ {
+ "type": "arrow",
+ "version": 6015,
+ "versionNonce": 557789332,
+ "isDeleted": false,
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 975.1009683779257,
+ "y": 997.4977290869242,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 131.88364892217714,
+ "height": 1.5991466819614288,
+ "seed": 1799383101,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834948965,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "ufedJ6N2PtgoNmILgLeR9",
+ "focus": 0.07283746820412001,
+ "gap": 5.230860679797303
+ },
+ "endBinding": {
+ "elementId": "Li4WakMROOrh4VO9QnPFJ",
+ "focus": -0.2792738065431994,
+ "gap": 1
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 131.88364892217714,
+ 1.5991466819614288
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 5673,
+ "versionNonce": 1555299860,
+ "isDeleted": false,
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 885.1692255211542,
+ "y": 994.867507895086,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 128.6227925647902,
+ "height": 1.7720335439950077,
+ "seed": 1850003037,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669834948966,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "s7XdvhPQZXbsLet4haeHa",
+ "focus": 0.003950114346711942,
+ "gap": 14.016575327063322
+ },
+ "endBinding": {
+ "elementId": "8j6p54tqGlb2RxbOlwOCj",
+ "focus": 0.3601622230841939,
+ "gap": 1.7671977647119093
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -128.6227925647902,
+ 1.7720335439950077
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2499,
+ "versionNonce": 1615591956,
+ "isDeleted": false,
+ "id": "ZzAiVIq7d_EX6jT-x-V8F",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1497.9736796360964,
+ "y": 962.5622249540979,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 58,
+ "height": 26,
+ "seed": 11980573,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ICS-4",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ICS-4"
+ },
+ {
+ "type": "text",
+ "version": 3189,
+ "versionNonce": 477054868,
+ "isDeleted": false,
+ "id": "0rLEyoXAuW-fiLkQc16EL",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1291.3486796360967,
+ "y": 1027.7844471763203,
+ "strokeColor": "#2b8a3e",
+ "backgroundColor": "transparent",
+ "width": 139,
+ "height": 50,
+ "seed": 1106024147,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834954446,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV channel is\nestablished",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV channel is\nestablished"
+ },
+ {
+ "type": "text",
+ "version": 2752,
+ "versionNonce": 1001633044,
+ "isDeleted": false,
+ "id": "HQSTyus42WtiUMx7P8B2w",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1290.0986796360967,
+ "y": 901.3505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 161,
+ "height": 75,
+ "seed": 419778963,
+ "groupIds": [
+ "HoMXcPrQkkxUk2lcjsIgs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "dyJw9z0fJ3VE34NAIchq_",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927992,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "verify that\nunderlying client \nis ",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "verify that\nunderlying client \nis "
+ },
+ {
+ "type": "text",
+ "version": 2907,
+ "versionNonce": 1381815852,
+ "isDeleted": false,
+ "id": "r9Ob2NWj9-BSluAY_s8xJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1316.0986796360967,
+ "y": 953.3505569214933,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 1642104093,
+ "groupIds": [
+ "HoMXcPrQkkxUk2lcjsIgs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927993,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "text",
+ "version": 2880,
+ "versionNonce": 1237026068,
+ "isDeleted": false,
+ "id": "JuSJvOu_FnBmNVdp_eRD9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 371.47367963609713,
+ "y": 922.1005569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 161,
+ "height": 75,
+ "seed": 238396435,
+ "groupIds": [
+ "ty8fZRuUHvndHArhAo-GY"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "verify that\nunderlying client \nis ",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "verify that\nunderlying client \nis "
+ },
+ {
+ "type": "text",
+ "version": 3038,
+ "versionNonce": 1702495788,
+ "isDeleted": false,
+ "id": "PzxRjY9RfeJDTFHPi-mxg",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 397.47367963609713,
+ "y": 974.1005569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 1416927901,
+ "groupIds": [
+ "ty8fZRuUHvndHArhAo-GY"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "text",
+ "version": 3227,
+ "versionNonce": 1269940884,
+ "isDeleted": false,
+ "id": "VLOsPFs0wdBnJa4SDNUYt",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 370.22367963609713,
+ "y": 1148.8505569214933,
+ "strokeColor": "#2b8a3e",
+ "backgroundColor": "transparent",
+ "width": 139,
+ "height": 50,
+ "seed": 1131642451,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV channel is\nestablished",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV channel is\nestablished"
+ },
+ {
+ "type": "text",
+ "version": 3526,
+ "versionNonce": 357279660,
+ "isDeleted": false,
+ "id": "yDlrCkt69Ox28Fd3JSBLU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1290.0986796360967,
+ "y": 1098.4755569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 187,
+ "height": 125,
+ "seed": 1470041133,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834917273,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if not\ntransferChannelId \nthen init transfer \nchannel handshake\n(ICS-20)",
+ "baseline": 118,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if not\ntransferChannelId \nthen init transfer \nchannel handshake\n(ICS-20)"
+ },
+ {
+ "type": "rectangle",
+ "version": 2454,
+ "versionNonce": 2130989076,
+ "isDeleted": false,
+ "id": "OqQOU1nnCT4lg6pSCaQ7a",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 20.89034630276359,
+ "y": 511.26722358816005,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 269,
+ "height": 94,
+ "seed": 1582902036,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "type": "text",
+ "id": "zDVEhmmxBzSJBhJHJmVHc"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 155,
+ "versionNonce": 506948396,
+ "isDeleted": false,
+ "id": "zDVEhmmxBzSJBhJHJmVHc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 25.89034630276359,
+ "y": 516.26722358816,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 248,
+ "height": 75,
+ "seed": 657361684,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ConsumerAdditionProposal\n- chainId\n- ?transferChannelId",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "OqQOU1nnCT4lg6pSCaQ7a",
+ "originalText": "ConsumerAdditionProposal\n- chainId\n- ?transferChannelId"
+ },
+ {
+ "type": "rectangle",
+ "version": 2637,
+ "versionNonce": 329854356,
+ "isDeleted": false,
+ "id": "4SFSWAOwjrAh8IJkK6-z_",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 808.7236796360969,
+ "y": 335.1005569214933,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 269,
+ "height": 167,
+ "seed": 551949868,
+ "groupIds": [
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "XfAY8vt2i5RqiegUfuGnl",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "XfAY8vt2i5RqiegUfuGnl"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 315,
+ "versionNonce": 955852204,
+ "isDeleted": false,
+ "id": "XfAY8vt2i5RqiegUfuGnl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 813.7236796360969,
+ "y": 340.1005569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 206,
+ "height": 150,
+ "seed": 1489941140,
+ "groupIds": [
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "\n- preCCV = false\n-\n-\n- \n- ?transferChannelId",
+ "baseline": 143,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "4SFSWAOwjrAh8IJkK6-z_",
+ "originalText": "\n- preCCV = false\n-\n-\n- \n- ?transferChannelId"
+ },
+ {
+ "type": "text",
+ "version": 1815,
+ "versionNonce": 1255562004,
+ "isDeleted": false,
+ "id": "Sh1XXslrX58fsshVvhyW9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 815.4736796360969,
+ "y": 339.1005569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 183,
+ "height": 25,
+ "seed": 1473545620,
+ "groupIds": [
+ "ARoJuzpA2IQG7JRoQ8WXv",
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV genesis state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV genesis state"
+ },
+ {
+ "type": "text",
+ "version": 1939,
+ "versionNonce": 949981228,
+ "isDeleted": false,
+ "id": "OswdviM2jC8xn7pkFOF72",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 831.4736796360967,
+ "y": 389.1005569214933,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 121,
+ "height": 25,
+ "seed": 1154359956,
+ "groupIds": [
+ "gSkI5TZIKHBRDvEKXpAAU",
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client state"
+ },
+ {
+ "type": "text",
+ "version": 1954,
+ "versionNonce": 1325521044,
+ "isDeleted": false,
+ "id": "-gGeCBbE3mgCUqU61gtxL",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 831.2236796360967,
+ "y": 414.6005569214933,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 164,
+ "height": 25,
+ "seed": 1115561772,
+ "groupIds": [
+ "L-LQOwH5jgwypecRIeP3i",
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "consensus state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "consensus state"
+ },
+ {
+ "type": "text",
+ "version": 2055,
+ "versionNonce": 2098506412,
+ "isDeleted": false,
+ "id": "1U-6UVsjeaamMGZhYxuqN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 832.4736796360967,
+ "y": 439.6005569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 134,
+ "height": 25,
+ "seed": 483616532,
+ "groupIds": [
+ "w81ZdVAhViBb0gC9BoWL0",
+ "FV967v4Rc2ZX6626FweUk"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "validator set",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "validator set"
+ },
+ {
+ "type": "text",
+ "version": 1733,
+ "versionNonce": 199337492,
+ "isDeleted": false,
+ "id": "DW9Eaom1oslXkxqtWT0mP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 374.7236796360969,
+ "y": 592.8505569214934,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 65,
+ "height": 26,
+ "seed": 1147826580,
+ "groupIds": [
+ "6O5GHTrX13JNJgzJAUdfl"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "create",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "create"
+ },
+ {
+ "type": "text",
+ "version": 1927,
+ "versionNonce": 1493333292,
+ "isDeleted": false,
+ "id": "g5pA95MbLfBZEEvvc9hiH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 371.7236796360969,
+ "y": 624.1005569214934,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 183,
+ "height": 25,
+ "seed": 1658724780,
+ "groupIds": [
+ "6O5GHTrX13JNJgzJAUdfl"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834876873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV genesis state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV genesis state"
+ },
+ {
+ "type": "text",
+ "version": 3262,
+ "versionNonce": 790885012,
+ "isDeleted": false,
+ "id": "hCvso3bMoB4PxzmJv-kzb",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1290.0986796360967,
+ "y": 612.1005569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 137,
+ "height": 50,
+ "seed": 227809708,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927993,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init connection\nhandshake",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init connection\nhandshake"
+ },
+ {
+ "type": "text",
+ "version": 3354,
+ "versionNonce": 993603756,
+ "isDeleted": false,
+ "id": "oxCZ7QP1evlATXROlRRZe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1290.0986796360967,
+ "y": 673.3505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 109,
+ "height": 50,
+ "seed": 935366700,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669834927993,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init channel\nhandshake",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init channel\nhandshake"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.png
new file mode 100644
index 00000000..9e8b5f14
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-init-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.excalidraw
new file mode 100644
index 00000000..0151bc20
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.excalidraw
@@ -0,0 +1,1228 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "id": "dXGJbtsNjdX6vZM0RiO1T",
+ "type": "rectangle",
+ "x": 366,
+ "y": 496.25,
+ "width": 41,
+ "height": 29,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 2017127294,
+ "version": 106,
+ "versionNonce": 1870083490,
+ "isDeleted": false,
+ "boundElements": [
+ {
+ "id": "Lm4nyf2jtOzfTy2WKx1ry",
+ "type": "arrow"
+ },
+ {
+ "id": "2HX-46T1-oqbz1Mc9hw2S",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077161417,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 63,
+ "versionNonce": 1325002110,
+ "isDeleted": false,
+ "id": "giy7HVJulxkK5HDVUafCR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 511.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 41,
+ "height": 29,
+ "seed": 1636143522,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "0d1ff2WhpECdZM_G9B2dL",
+ "type": "arrow"
+ },
+ {
+ "id": "2HX-46T1-oqbz1Mc9hw2S",
+ "type": "arrow"
+ },
+ {
+ "id": "cRZCXc7Ob7Rt09-20pjsQ",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077161417,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 119,
+ "versionNonce": 640420862,
+ "isDeleted": false,
+ "id": "PGgh4NbRuMsEPsNitvOe7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 604.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 41,
+ "height": 29,
+ "seed": 587616034,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "cRZCXc7Ob7Rt09-20pjsQ",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077253368,
+ "link": null
+ },
+ {
+ "id": "FO-6rY7Tmc6PCy_7VvHcN",
+ "type": "text",
+ "x": 119,
+ "y": 390,
+ "width": 133,
+ "height": 25,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 2100651682,
+ "version": 155,
+ "versionNonce": 2098336674,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1646077127396,
+ "link": null,
+ "text": "provider chain",
+ "fontSize": 20,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 18,
+ "containerId": null,
+ "originalText": "provider chain"
+ },
+ {
+ "type": "arrow",
+ "version": 449,
+ "versionNonce": 2031774462,
+ "isDeleted": false,
+ "id": "Lm4nyf2jtOzfTy2WKx1ry",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.9158370835549663,
+ "x": 324.87753769206023,
+ "y": 451.9621486101987,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 80.40773254541557,
+ "height": 12.045428727381761,
+ "seed": 526143522,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646076973097,
+ "link": null,
+ "startBinding": {
+ "elementId": "f3rhcRwGwsc5cF6ZHFK1U",
+ "focus": -0.26362793863884676,
+ "gap": 6.068468469446316
+ },
+ "endBinding": {
+ "elementId": "dXGJbtsNjdX6vZM0RiO1T",
+ "focus": 0.2905303456986677,
+ "gap": 4.325154663594503
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 80.40773254541557,
+ 12.045428727381761
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 155,
+ "versionNonce": 1034958526,
+ "isDeleted": false,
+ "id": "f3rhcRwGwsc5cF6ZHFK1U",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 305.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 41,
+ "height": 29,
+ "seed": 1771322750,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Lm4nyf2jtOzfTy2WKx1ry",
+ "type": "arrow"
+ },
+ {
+ "id": "kVHmz2iFaAgRy7VJ0HLxb",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077157742,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 110,
+ "versionNonce": 1570905122,
+ "isDeleted": false,
+ "id": "JQXAIk3s3ohRpLTk0afD1",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 448.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 41,
+ "height": 29,
+ "seed": 336765822,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "0d1ff2WhpECdZM_G9B2dL",
+ "type": "arrow"
+ },
+ {
+ "id": "kVHmz2iFaAgRy7VJ0HLxb",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077157743,
+ "link": null
+ },
+ {
+ "type": "arrow",
+ "version": 285,
+ "versionNonce": 668320098,
+ "isDeleted": false,
+ "id": "0d1ff2WhpECdZM_G9B2dL",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.9158370835549663,
+ "x": 469.2794757705825,
+ "y": 448.36084173002894,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 81.96259899829863,
+ "height": 14.147356569534566,
+ "seed": 715544994,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646076838899,
+ "link": null,
+ "startBinding": {
+ "elementId": "JQXAIk3s3ohRpLTk0afD1",
+ "focus": -0.4270673438242542,
+ "gap": 3.6957376563468074
+ },
+ "endBinding": {
+ "elementId": "giy7HVJulxkK5HDVUafCR",
+ "focus": 0.28071837566818364,
+ "gap": 5.684422421410943
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 81.96259899829863,
+ 14.147356569534566
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 146,
+ "versionNonce": 117824574,
+ "isDeleted": false,
+ "id": "xN2B--65W20EQnnK9zqk4",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 686.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 41,
+ "height": 29,
+ "seed": 520560318,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077256534,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 168,
+ "versionNonce": 79406206,
+ "isDeleted": false,
+ "id": "QmOH7gGmVbeNry8NY-CF5",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 768.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 41,
+ "height": 29,
+ "seed": 1284517922,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "aDjtcoHVjozFY7WuJY0yR",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077259418,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 170,
+ "versionNonce": 206386110,
+ "isDeleted": false,
+ "id": "2sTlmHj03RqCUvX54nQTm",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 565.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "width": 41,
+ "height": 29,
+ "seed": 212191074,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "aDjtcoHVjozFY7WuJY0yR",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077249965,
+ "link": null
+ },
+ {
+ "type": "arrow",
+ "version": 655,
+ "versionNonce": 1972673890,
+ "isDeleted": false,
+ "id": "aDjtcoHVjozFY7WuJY0yR",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.9158370835549663,
+ "x": 614.0734386587635,
+ "y": 500.30233235763797,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 163.46403792632998,
+ "height": 95.49037375842022,
+ "seed": 1707943586,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646076996810,
+ "link": null,
+ "startBinding": {
+ "elementId": "2sTlmHj03RqCUvX54nQTm",
+ "focus": 0.40404651949226894,
+ "gap": 3.6785331236621346
+ },
+ "endBinding": {
+ "elementId": "QmOH7gGmVbeNry8NY-CF5",
+ "focus": 0.8311181170846009,
+ "gap": 6.848349336980618
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 163.46403792632998,
+ -95.49037375842022
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 188,
+ "versionNonce": 1499557794,
+ "isDeleted": false,
+ "id": "IReRqSBbnbZztnt9Rd6PP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 638.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 41,
+ "height": 29,
+ "seed": 131138238,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077243457,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 179,
+ "versionNonce": 1204035390,
+ "isDeleted": false,
+ "id": "J0L7vWo7k58u3h-EgFZYO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 708.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 41,
+ "height": 29,
+ "seed": 370009698,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077157743,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 160,
+ "versionNonce": 494861218,
+ "isDeleted": false,
+ "id": "pIR-13bhdFhlezC9u8eLD",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 777.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 41,
+ "height": 29,
+ "seed": 2145813154,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077157743,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 176,
+ "versionNonce": 1400361122,
+ "isDeleted": false,
+ "id": "S5Xetp1_fDLpeKenxgEpl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 832.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 41,
+ "height": 29,
+ "seed": 1978641122,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077226732,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 173,
+ "versionNonce": 288308194,
+ "isDeleted": false,
+ "id": "t39A2oiw8FWgi_H03Fd1i",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 895.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#40c057",
+ "width": 41,
+ "height": 29,
+ "seed": 149853538,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "TJmI4fIZLIR63b40ULmAH",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077239499,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 158,
+ "versionNonce": 985237374,
+ "isDeleted": false,
+ "id": "d-ddNSH6LMpq3uA2yh0kI",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 844.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 41,
+ "height": 29,
+ "seed": 1586090082,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "TJmI4fIZLIR63b40ULmAH",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077157743,
+ "link": null
+ },
+ {
+ "type": "arrow",
+ "version": 494,
+ "versionNonce": 496935202,
+ "isDeleted": false,
+ "id": "TJmI4fIZLIR63b40ULmAH",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0.9158370835549663,
+ "x": 861.8739718628682,
+ "y": 440.4738823475756,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 73.16785906553775,
+ "height": 22.046516861368502,
+ "seed": 1346903266,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646076996811,
+ "link": null,
+ "startBinding": {
+ "elementId": "d-ddNSH6LMpq3uA2yh0kI",
+ "focus": -0.47506714799687233,
+ "gap": 4.102264548837752
+ },
+ "endBinding": {
+ "elementId": "t39A2oiw8FWgi_H03Fd1i",
+ "focus": 0.1462118275130385,
+ "gap": 7.441172969995762
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 73.16785906553775,
+ 22.046516861368502
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 159,
+ "versionNonce": 389639266,
+ "isDeleted": false,
+ "id": "fCVwLr6MRiUMi5yjg2BCP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 956.5,
+ "y": 496.25,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fab005",
+ "width": 41,
+ "height": 29,
+ "seed": 1698265954,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077231622,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 138,
+ "versionNonce": 1876004898,
+ "isDeleted": false,
+ "id": "cfkjSfrhUWSYi6gAYJynw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 909.5,
+ "y": 385,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fab005",
+ "width": 41,
+ "height": 29,
+ "seed": 938269822,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077235024,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 288,
+ "versionNonce": 1369336382,
+ "isDeleted": false,
+ "id": "KFcHQtQ4HSMxl-tDMFmTk",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 311.5,
+ "y": 443.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 42,
+ "height": 25,
+ "seed": 1604739042,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646076972349,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSC1",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSC1"
+ },
+ {
+ "type": "text",
+ "version": 292,
+ "versionNonce": 282378082,
+ "isDeleted": false,
+ "id": "BCvm6sdk2bzmPHsPNg8rV",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 517,
+ "y": 444.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 51,
+ "height": 25,
+ "seed": 1354420734,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646076838899,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSC2",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSC2"
+ },
+ {
+ "type": "text",
+ "version": 260,
+ "versionNonce": 2134716606,
+ "isDeleted": false,
+ "id": "IOJKXj77Mtw2wYY-J8egl",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 705,
+ "y": 434.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 50,
+ "height": 25,
+ "seed": 608588670,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646076996661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSC3",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSC3"
+ },
+ {
+ "type": "text",
+ "version": 241,
+ "versionNonce": 49922594,
+ "isDeleted": false,
+ "id": "jNIyXk3mgir_5nfCyHtlq",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 909,
+ "y": 436.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 49,
+ "height": 25,
+ "seed": 1324593634,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646076996661,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSC4",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSC4"
+ },
+ {
+ "type": "arrow",
+ "version": 419,
+ "versionNonce": 1749536354,
+ "isDeleted": false,
+ "id": "7Xh5sJZCt--M6HS0iLol7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 6.25438768430974,
+ "x": 325.8098335597477,
+ "y": 329.8859185932823,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 0.8096700170103759,
+ "height": 41.59500930334184,
+ "seed": 42424930,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646077020419,
+ "link": null,
+ "startBinding": {
+ "elementId": "eSzN3tC3e2MVTc49PxUdR",
+ "focus": -0.33520536080054925,
+ "gap": 13.369584039732558
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.8096700170103759,
+ 41.59500930334184
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 413,
+ "versionNonce": 337859298,
+ "isDeleted": false,
+ "id": "eSzN3tC3e2MVTc49PxUdR",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 245.5,
+ "y": 289.519524154067,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 119,
+ "height": 25,
+ "seed": 1799097762,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "7Xh5sJZCt--M6HS0iLol7",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077148172,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-1",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-1"
+ },
+ {
+ "type": "arrow",
+ "version": 388,
+ "versionNonce": 1244962786,
+ "isDeleted": false,
+ "id": "D6FMHkxWilLihXdUuF1l5",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 465.4336507169435,
+ "y": 326.23964063810354,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 0.4289559332560202,
+ "height": 41.22131105376235,
+ "seed": 1559910050,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646077017569,
+ "link": null,
+ "startBinding": {
+ "elementId": "34CMnXniITHjgUQkEwWxO",
+ "focus": -0.003216876081784181,
+ "gap": 13.700592329969481
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.4289559332560202,
+ 41.22131105376235
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 433,
+ "versionNonce": 815350846,
+ "isDeleted": false,
+ "id": "34CMnXniITHjgUQkEwWxO",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 401.5,
+ "y": 289.519524154067,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 128,
+ "height": 25,
+ "seed": 1198873726,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "D6FMHkxWilLihXdUuF1l5",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077148172,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-2",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-2"
+ },
+ {
+ "type": "arrow",
+ "version": 332,
+ "versionNonce": 74209854,
+ "isDeleted": false,
+ "id": "vx1ePYG_g6Niz68an3RX0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 589.4336507169435,
+ "y": 327.23964063810354,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 0.4289559332560202,
+ "height": 41.22131105376235,
+ "seed": 12318690,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646077141123,
+ "link": null,
+ "startBinding": {
+ "elementId": "WbHIHHbvZbFgZy4t1BRL_",
+ "focus": 0.5428734128179646,
+ "gap": 11.700592329969481
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.4289559332560202,
+ 41.22131105376235
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 403,
+ "versionNonce": 867870370,
+ "isDeleted": false,
+ "id": "WbHIHHbvZbFgZy4t1BRL_",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 560.5,
+ "y": 289.519524154067,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 128,
+ "height": 25,
+ "seed": 1929998142,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "vx1ePYG_g6Niz68an3RX0",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077148172,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-3",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-3"
+ },
+ {
+ "type": "arrow",
+ "version": 332,
+ "versionNonce": 900042658,
+ "isDeleted": false,
+ "id": "BD71nC83W4ZTghFu8otbB",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 864.4336507169435,
+ "y": 325.23964063810354,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 0.4289559332560202,
+ "height": 41.22131105376235,
+ "seed": 555091106,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1646077142981,
+ "link": null,
+ "startBinding": {
+ "elementId": "LxjWLi54P0ijxSwaGt3_w",
+ "focus": 0.31909607184067174,
+ "gap": 12.700592329969481
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.4289559332560202,
+ 41.22131105376235
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 332,
+ "versionNonce": 2007108734,
+ "isDeleted": false,
+ "id": "LxjWLi54P0ijxSwaGt3_w",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 821.5,
+ "y": 289.519524154067,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 25,
+ "seed": 649301630,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "BD71nC83W4ZTghFu8otbB",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1646077148172,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-4",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-4"
+ },
+ {
+ "type": "text",
+ "version": 233,
+ "versionNonce": 2120715874,
+ "isDeleted": false,
+ "id": "uSebukr-eizrDRBECjcYu",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 162.5,
+ "y": 500.5,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 143,
+ "height": 25,
+ "seed": 1143774590,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1646077130915,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "consumer chain",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "consumer chain"
+ },
+ {
+ "id": "fHJhTCuZjkQbKYo1h-GSG",
+ "type": "text",
+ "x": 154,
+ "y": 229,
+ "width": 800,
+ "height": 35,
+ "angle": 0,
+ "strokeColor": "#000000",
+ "backgroundColor": "#4c6ef5",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "seed": 286155966,
+ "version": 190,
+ "versionNonce": 1460853410,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1646077420994,
+ "link": null,
+ "text": "Intuition of mapping consumer heights to provider heights ",
+ "fontSize": 28,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "baseline": 25,
+ "containerId": null,
+ "originalText": "Intuition of mapping consumer heights to provider heights "
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.png
new file mode 100644
index 00000000..2cefc6dd
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-mapping-intuition.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.excalidraw
new file mode 100644
index 00000000..e1afeadf
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.excalidraw
@@ -0,0 +1,2455 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 2275,
+ "versionNonce": 141366489,
+ "isDeleted": false,
+ "id": "VyRoLp37OULOh8PtwSL1o",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 320.80701296942993,
+ "y": 675.1733360652088,
+ "strokeColor": "#ced4da",
+ "backgroundColor": "#ced4da",
+ "width": 1285.166666666667,
+ "height": 469.94444444444434,
+ "seed": 1900272787,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972378947,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1426,
+ "versionNonce": 955279351,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 542.3333333333333,
+ "y": 251.05555555555554,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 589,
+ "height": 35,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "CCV - Channel Initialization: Existing Chain",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV - Channel Initialization: Existing Chain"
+ },
+ {
+ "type": "text",
+ "version": 567,
+ "versionNonce": 1520662265,
+ "isDeleted": false,
+ "id": "ghfMBQkamou-QJWwVrzMO",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 355.88888888888886,
+ "y": 400.5,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 138,
+ "height": 26,
+ "seed": 1747022505,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1312,
+ "versionNonce": 1458165401,
+ "isDeleted": false,
+ "id": "ONYRFM5fo0s39BeH4ty3j",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1112.833333333333,
+ "y": 1175.638888888889,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 148,
+ "height": 26,
+ "seed": 129325673,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669972422261,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chain"
+ },
+ {
+ "type": "rectangle",
+ "version": 1080,
+ "versionNonce": 327304089,
+ "isDeleted": false,
+ "id": "8j6p54tqGlb2RxbOlwOCj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 346.668124080541,
+ "y": 435.978891620764,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 408.1111111111111,
+ "height": 918.3333333333334,
+ "seed": 38506771,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972432728,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 1193,
+ "versionNonce": 28352825,
+ "isDeleted": false,
+ "id": "Li4WakMROOrh4VO9QnPFJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1106.1959018583188,
+ "y": 375.3400027318751,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 424.6111111111107,
+ "height": 783.0555555555559,
+ "seed": 1423042963,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972361295,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 2758,
+ "versionNonce": 1854167129,
+ "isDeleted": false,
+ "id": "cN8JVC0YU7ylsJMNyoLMx",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1283.4459018583188,
+ "y": 554.2011138429866,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 224.25,
+ "height": 580.2777777777779,
+ "seed": 1979822931,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "dXiPD3WzFtcIXoXAUQVpo",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "dyJw9z0fJ3VE34NAIchq_",
+ "type": "arrow"
+ },
+ {
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972348251,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1028,
+ "versionNonce": 385890135,
+ "isDeleted": false,
+ "id": "Iw9eosLogOHYo9M8Jsbr3",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1317.4459018583188,
+ "y": 528.2011138429866,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 378901469,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "arrow",
+ "version": 6536,
+ "versionNonce": 1140845143,
+ "isDeleted": false,
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 579.8318289527045,
+ "y": 567.5554928435379,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 510.58677663614606,
+ "height": 2.99739515287456,
+ "seed": 1757145171,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972442968,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": -0.8013344690644227,
+ "gap": 20.052593761052208
+ },
+ "endBinding": {
+ "elementId": "Li4WakMROOrh4VO9QnPFJ",
+ "focus": 0.4964072895220594,
+ "gap": 15.777296269468138
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 510.58677663614606,
+ 2.99739515287456
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1832,
+ "versionNonce": 1096505433,
+ "isDeleted": false,
+ "id": "obb5_vEy0bQhgCVmGyg2U",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 839.4165850810259,
+ "y": 510.61778050965324,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 174,
+ "height": 50,
+ "seed": 1080124915,
+ "groupIds": [
+ "3L8N_qI1cIkTh8PTodA99"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972760226,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "add CCV module \nto existing chain ",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "add CCV module \nto existing chain "
+ },
+ {
+ "type": "rectangle",
+ "version": 2460,
+ "versionNonce": 1999848023,
+ "isDeleted": false,
+ "id": "fatyAFozygc0HN-v5nRU0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 362.6959018583192,
+ "y": 482.0622249540977,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 197.08333333333317,
+ "height": 858.3333333333336,
+ "seed": 135825021,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972455773,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "arrow",
+ "version": 4577,
+ "versionNonce": 213271095,
+ "isDeleted": false,
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 293.12712540922803,
+ "y": 560.7697878216499,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 61.2971337489638,
+ "height": 2.638544725628435,
+ "seed": 679224755,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972448481,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "OqQOU1nnCT4lg6pSCaQ7a",
+ "focus": 0.21183993021769282,
+ "gap": 1.9867791064644393
+ },
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": 0.825308040723725,
+ "gap": 8.271642700127359
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 61.2971337489638,
+ -2.638544725628435
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 982,
+ "versionNonce": 1146944345,
+ "isDeleted": false,
+ "id": "DqEozYcKYxDC28R2NnxW0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 411.5570129694301,
+ "y": 446.2011138429867,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 279128861,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 2752,
+ "versionNonce": 1286684569,
+ "isDeleted": false,
+ "id": "6aTTvXXhRp_Om_uUJ18Yj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 859.2434333771513,
+ "y": 696.9788916207647,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 178,
+ "height": 26,
+ "seed": 180182749,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972462829,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "channel handshake",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "channel handshake"
+ },
+ {
+ "type": "text",
+ "version": 2127,
+ "versionNonce": 1370261815,
+ "isDeleted": false,
+ "id": "ZUY3S70EAhRzG6opdy45g",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1108.0152089590333,
+ "y": 682.3373046083263,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 151,
+ "height": 26,
+ "seed": 1975458397,
+ "groupIds": [
+ "MKCuh3Ezvdojoiw9C9BJi"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972347228,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenInit",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenInit"
+ },
+ {
+ "type": "arrow",
+ "version": 5156,
+ "versionNonce": 967239543,
+ "isDeleted": false,
+ "id": "dyJw9z0fJ3VE34NAIchq_",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1111.3275853573923,
+ "y": 725.3690346047606,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 159.7789284780024,
+ "height": 0.8821154837639824,
+ "seed": 488375795,
+ "groupIds": [
+ "MKCuh3Ezvdojoiw9C9BJi"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972348250,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "cN8JVC0YU7ylsJMNyoLMx",
+ "focus": 0.4145724911893395,
+ "gap": 12.33938802292414
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 159.7789284780024,
+ -0.8821154837639824
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2221,
+ "versionNonce": 869550679,
+ "isDeleted": false,
+ "id": "OLVnAm75K0c9kLnrUS8a3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1107.881446312339,
+ "y": 811.9352488447694,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 148,
+ "height": 26,
+ "seed": 1042999091,
+ "groupIds": [
+ "zm2GFhRP96ueQ_yNG_kXo"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972347228,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenAck",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenAck"
+ },
+ {
+ "type": "arrow",
+ "version": 5129,
+ "versionNonce": 1634263191,
+ "isDeleted": false,
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1109.693822710698,
+ "y": 854.9669788412034,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 161.68843185701462,
+ "height": 0.8091508609747962,
+ "seed": 1002477949,
+ "groupIds": [
+ "zm2GFhRP96ueQ_yNG_kXo"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972348251,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "cN8JVC0YU7ylsJMNyoLMx",
+ "focus": -0.03163514794473533,
+ "gap": 12.06364729060624
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 161.68843185701462,
+ -0.8091508609747962
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1918,
+ "versionNonce": 1334757145,
+ "isDeleted": false,
+ "id": "1i9SM2FmlaLGLKGuMadr3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 561.4522437933846,
+ "y": 992.3135754416971,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 184,
+ "height": 26,
+ "seed": 23310525,
+ "groupIds": [
+ "CNZ2bx0Kmyw6NZbWgff33"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972453529,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenConfirm",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenConfirm"
+ },
+ {
+ "type": "arrow",
+ "version": 4614,
+ "versionNonce": 1066964185,
+ "isDeleted": false,
+ "id": "jz87Jfblhql4prXUNauec",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 744.1827595896757,
+ "y": 1036.33156341232,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 178.3329820370053,
+ "height": 1.7496207741528451,
+ "seed": 2088404371,
+ "groupIds": [
+ "CNZ2bx0Kmyw6NZbWgff33"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972455773,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": 0.2843924394760302,
+ "gap": 6.070542361018056
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -178.3329820370053,
+ -1.7496207741528451
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1739,
+ "versionNonce": 809008121,
+ "isDeleted": false,
+ "id": "tFHjTvU-Pd7PxTc9LTr4w",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 591.945901858319,
+ "y": 751.7754531910084,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 149,
+ "height": 26,
+ "seed": 19313843,
+ "groupIds": [
+ "vXfReGIEP1cvs4DENwZ3s"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972453529,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "OnChanOpenTry",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "OnChanOpenTry"
+ },
+ {
+ "type": "arrow",
+ "version": 4449,
+ "versionNonce": 558010809,
+ "isDeleted": false,
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 748.0653065434985,
+ "y": 795.7934411616313,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 177.7865820739139,
+ "height": 2.586240593854086,
+ "seed": 290612221,
+ "groupIds": [
+ "vXfReGIEP1cvs4DENwZ3s"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972455773,
+ "link": null,
+ "locked": false,
+ "startBinding": null,
+ "endBinding": {
+ "elementId": "fatyAFozygc0HN-v5nRU0",
+ "focus": -0.27777022964338716,
+ "gap": 10.499489277932298
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -177.7865820739139,
+ -2.586240593854086
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1142,
+ "versionNonce": 1337443257,
+ "isDeleted": false,
+ "id": "NMo9QrIPRXbhU3R-JdTPp",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1117.4966157758395,
+ "y": 532.240804400325,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 109.23098275619242,
+ "height": 25.818232287827296,
+ "seed": 1048478237,
+ "groupIds": [
+ "qkwovs2kx-e3xwC2tuyLC"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "dXiPD3WzFtcIXoXAUQVpo",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 19.860178682944078,
+ "fontFamily": 1,
+ "text": "InitGenesis",
+ "baseline": 17.818232287827296,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "InitGenesis"
+ },
+ {
+ "type": "arrow",
+ "version": 4918,
+ "versionNonce": 863662233,
+ "isDeleted": false,
+ "id": "dXiPD3WzFtcIXoXAUQVpo",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1117.1958915115365,
+ "y": 569.9498037323456,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 151.34843960071998,
+ "height": 0.32892941983880064,
+ "seed": 1345845811,
+ "groupIds": [
+ "qkwovs2kx-e3xwC2tuyLC"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972337558,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "NMo9QrIPRXbhU3R-JdTPp",
+ "focus": 1.8944493333044778,
+ "gap": 11.890767044193183
+ },
+ "endBinding": {
+ "elementId": "cN8JVC0YU7ylsJMNyoLMx",
+ "focus": 0.9470099932418258,
+ "gap": 14.901570746062362
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 151.34843960071998,
+ -0.32892941983880064
+ ]
+ ]
+ },
+ {
+ "type": "diamond",
+ "version": 1747,
+ "versionNonce": 28762743,
+ "isDeleted": false,
+ "id": "ufedJ6N2PtgoNmILgLeR9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 887.7539826663998,
+ "y": 723.3122249540979,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 84.27272727272718,
+ "height": 71.99999999999996,
+ "seed": 1428807773,
+ "groupIds": [
+ "IcSLxZ0ZPqcKuPbEB_1Pa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972462829,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1669,
+ "versionNonce": 1002535831,
+ "isDeleted": false,
+ "id": "s7XdvhPQZXbsLet4haeHa",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 900.4358008482178,
+ "y": 748.6758613177344,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 58.90909090909091,
+ "height": 21.272727272727266,
+ "seed": 1395171315,
+ "groupIds": [
+ "IcSLxZ0ZPqcKuPbEB_1Pa"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972462829,
+ "link": null,
+ "locked": false,
+ "fontSize": 16.363636363636367,
+ "fontFamily": 1,
+ "text": "Relayer",
+ "baseline": 14.272727272727266,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": null,
+ "originalText": "Relayer"
+ },
+ {
+ "type": "arrow",
+ "version": 6378,
+ "versionNonce": 506118615,
+ "isDeleted": false,
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 976.9370131128985,
+ "y": 761.1152297220069,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 129.50888874542022,
+ "height": 0.18739369078400614,
+ "seed": 1799383101,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972466530,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "ufedJ6N2PtgoNmILgLeR9",
+ "focus": 0.048192508367263284,
+ "gap": 4.560429420967612
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 129.50888874542022,
+ 0.18739369078400614
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 6037,
+ "versionNonce": 289566455,
+ "isDeleted": false,
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 886.4192255211543,
+ "y": 758.7419841303954,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 128.1399903295021,
+ "height": 0.9161487527437657,
+ "seed": 1850003037,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1669972466530,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "s7XdvhPQZXbsLet4haeHa",
+ "focus": 0.08122481458833257,
+ "gap": 14.016575327063435
+ },
+ "endBinding": {
+ "elementId": "8j6p54tqGlb2RxbOlwOCj",
+ "focus": -0.2909162957594993,
+ "gap": 3.5000000000001137
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -128.1399903295021,
+ 0.9161487527437657
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2504,
+ "versionNonce": 1318670937,
+ "isDeleted": false,
+ "id": "ZzAiVIq7d_EX6jT-x-V8F",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1544.2236796360964,
+ "y": 761.3122249540979,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 58,
+ "height": 26,
+ "seed": 11980573,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ICS-4",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "ICS-4"
+ },
+ {
+ "type": "text",
+ "version": 3230,
+ "versionNonce": 1125231895,
+ "isDeleted": false,
+ "id": "0rLEyoXAuW-fiLkQc16EL",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1294.1820129694302,
+ "y": 830.2844471763203,
+ "strokeColor": "#2b8a3e",
+ "backgroundColor": "transparent",
+ "width": 139,
+ "height": 50,
+ "seed": 1106024147,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972317976,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV channel is\nestablished",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV channel is\nestablished"
+ },
+ {
+ "type": "text",
+ "version": 2757,
+ "versionNonce": 1548721977,
+ "isDeleted": false,
+ "id": "HQSTyus42WtiUMx7P8B2w",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1292.9320129694302,
+ "y": 686.3505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 161,
+ "height": 75,
+ "seed": 419778963,
+ "groupIds": [
+ "HoMXcPrQkkxUk2lcjsIgs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "verify that\nunderlying client \nis ",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "verify that\nunderlying client \nis "
+ },
+ {
+ "type": "text",
+ "version": 2912,
+ "versionNonce": 157797591,
+ "isDeleted": false,
+ "id": "r9Ob2NWj9-BSluAY_s8xJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1318.9320129694302,
+ "y": 738.3505569214933,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 1642104093,
+ "groupIds": [
+ "HoMXcPrQkkxUk2lcjsIgs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "text",
+ "version": 2885,
+ "versionNonce": 2070499353,
+ "isDeleted": false,
+ "id": "JuSJvOu_FnBmNVdp_eRD9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 377.72367963609713,
+ "y": 750.8505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 161,
+ "height": 75,
+ "seed": 238396435,
+ "groupIds": [
+ "ty8fZRuUHvndHArhAo-GY"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "verify that\nunderlying client \nis ",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "verify that\nunderlying client \nis "
+ },
+ {
+ "type": "text",
+ "version": 3043,
+ "versionNonce": 1185290743,
+ "isDeleted": false,
+ "id": "PzxRjY9RfeJDTFHPi-mxg",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 403.72367963609713,
+ "y": 802.8505569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 26,
+ "seed": 1416927901,
+ "groupIds": [
+ "ty8fZRuUHvndHArhAo-GY"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client"
+ },
+ {
+ "type": "text",
+ "version": 3307,
+ "versionNonce": 1540393017,
+ "isDeleted": false,
+ "id": "VLOsPFs0wdBnJa4SDNUYt",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 371.47367963609713,
+ "y": 1013.8505569214933,
+ "strokeColor": "#2b8a3e",
+ "backgroundColor": "transparent",
+ "width": 139,
+ "height": 50,
+ "seed": 1131642451,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972372428,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV channel is\nestablished",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV channel is\nestablished"
+ },
+ {
+ "type": "text",
+ "version": 3566,
+ "versionNonce": 1912378329,
+ "isDeleted": false,
+ "id": "yDlrCkt69Ox28Fd3JSBLU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1294.1820129694302,
+ "y": 895.9755569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 187,
+ "height": 125,
+ "seed": 1470041133,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "0imTdwpoy104tRk4pp9Cf",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972317976,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "if not\ntransferChannelId \nthen init transfer \nchannel handshake\n(ICS-20)",
+ "baseline": 118,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "if not\ntransferChannelId \nthen init transfer \nchannel handshake\n(ICS-20)"
+ },
+ {
+ "type": "text",
+ "version": 1738,
+ "versionNonce": 2066013657,
+ "isDeleted": false,
+ "id": "DW9Eaom1oslXkxqtWT0mP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 373.4736796360969,
+ "y": 550.3505569214934,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 65,
+ "height": 26,
+ "seed": 1147826580,
+ "groupIds": [
+ "6O5GHTrX13JNJgzJAUdfl"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "create",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "create"
+ },
+ {
+ "type": "text",
+ "version": 1931,
+ "versionNonce": 1552107575,
+ "isDeleted": false,
+ "id": "g5pA95MbLfBZEEvvc9hiH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 370.4736796360969,
+ "y": 581.6005569214934,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 183,
+ "height": 25,
+ "seed": 1658724780,
+ "groupIds": [
+ "6O5GHTrX13JNJgzJAUdfl"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV genesis state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV genesis state"
+ },
+ {
+ "type": "text",
+ "version": 3358,
+ "versionNonce": 375241401,
+ "isDeleted": false,
+ "id": "oxCZ7QP1evlATXROlRRZe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1292.9320129694302,
+ "y": 573.3505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 109,
+ "height": 50,
+ "seed": 935366700,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init channel\nhandshake",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init channel\nhandshake"
+ },
+ {
+ "type": "rectangle",
+ "version": 2458,
+ "versionNonce": 1400580439,
+ "isDeleted": false,
+ "id": "OqQOU1nnCT4lg6pSCaQ7a",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 22.14034630276359,
+ "y": 498.76722358816005,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 269,
+ "height": 110,
+ "seed": 1582902036,
+ "groupIds": [
+ "mXHs0bhHy5SZ54HPb9hb0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "type": "text",
+ "id": "zDVEhmmxBzSJBhJHJmVHc"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 159,
+ "versionNonce": 338164633,
+ "isDeleted": false,
+ "id": "zDVEhmmxBzSJBhJHJmVHc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 27.14034630276359,
+ "y": 503.76722358816005,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 248,
+ "height": 100,
+ "seed": 657361684,
+ "groupIds": [
+ "mXHs0bhHy5SZ54HPb9hb0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "ConsumerAdditionProposal\n- chainId\n- \n- ?transferChannelId",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "OqQOU1nnCT4lg6pSCaQ7a",
+ "originalText": "ConsumerAdditionProposal\n- chainId\n- \n- ?transferChannelId"
+ },
+ {
+ "type": "text",
+ "version": 2043,
+ "versionNonce": 577597996,
+ "isDeleted": false,
+ "id": "KD2pmGyZ61NVLs1fUx-HT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 45.68201296943005,
+ "y": 551.3505569214933,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 64,
+ "height": 25,
+ "seed": 1348655276,
+ "groupIds": [
+ "hTQ5nA7l3AzWdJ7ldgp1g",
+ "JUBJ1IuXsWZpt_khzPReD",
+ "mXHs0bhHy5SZ54HPb9hb0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669833263873,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "connId",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "connId"
+ },
+ {
+ "type": "rectangle",
+ "version": 2641,
+ "versionNonce": 521791095,
+ "isDeleted": false,
+ "id": "4SFSWAOwjrAh8IJkK6-z_",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 787.4736796360969,
+ "y": 347.6005569214933,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 269,
+ "height": 142,
+ "seed": 551949868,
+ "groupIds": [
+ "5OuSB67r00L22GZWbpD8P"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "XfAY8vt2i5RqiegUfuGnl",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "XfAY8vt2i5RqiegUfuGnl"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 319,
+ "versionNonce": 1689723001,
+ "isDeleted": false,
+ "id": "XfAY8vt2i5RqiegUfuGnl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 792.4736796360969,
+ "y": 352.6005569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 206,
+ "height": 125,
+ "seed": 1489941140,
+ "groupIds": [
+ "5OuSB67r00L22GZWbpD8P"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "\n- preCCV = true\n-\n- \n- ?transferChannelId",
+ "baseline": 118,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "4SFSWAOwjrAh8IJkK6-z_",
+ "originalText": "\n- preCCV = true\n-\n- \n- ?transferChannelId"
+ },
+ {
+ "type": "text",
+ "version": 1819,
+ "versionNonce": 1042546583,
+ "isDeleted": false,
+ "id": "Sh1XXslrX58fsshVvhyW9",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 794.2236796360969,
+ "y": 351.6005569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 183,
+ "height": 25,
+ "seed": 1473545620,
+ "groupIds": [
+ "ARoJuzpA2IQG7JRoQ8WXv",
+ "5OuSB67r00L22GZWbpD8P"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV genesis state",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV genesis state"
+ },
+ {
+ "type": "text",
+ "version": 2059,
+ "versionNonce": 634300761,
+ "isDeleted": false,
+ "id": "1U-6UVsjeaamMGZhYxuqN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 810.9736796360967,
+ "y": 425.8505569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 197,
+ "height": 25,
+ "seed": 483616532,
+ "groupIds": [
+ "w81ZdVAhViBb0gC9BoWL0",
+ "5OuSB67r00L22GZWbpD8P"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972186023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "initial validator set",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "initial validator set"
+ },
+ {
+ "type": "text",
+ "version": 2072,
+ "versionNonce": 813054252,
+ "isDeleted": false,
+ "id": "V6Xt05k5XODhEDtzt7Elh",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 811.93201296943,
+ "y": 401.3505569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 64,
+ "height": 25,
+ "seed": 624581164,
+ "groupIds": [
+ "-uSvzwEE4x2FexFYQPSFp",
+ "5OuSB67r00L22GZWbpD8P"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669833183785,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "connId",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "connId"
+ },
+ {
+ "type": "text",
+ "version": 3724,
+ "versionNonce": 682226231,
+ "isDeleted": false,
+ "id": "BoYjBg4HiiHiXHNnBlVph",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1294.1820129694302,
+ "y": 1036.3505569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 212,
+ "height": 50,
+ "seed": 319076652,
+ "groupIds": [
+ "5rbXSCIsc4jBUQLPW6y0F"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972317976,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "replace validator set\nwith ",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "replace validator set\nwith "
+ },
+ {
+ "type": "text",
+ "version": 2408,
+ "versionNonce": 80654521,
+ "isDeleted": false,
+ "id": "P7RhZvjav3YSv-18-XCvh",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1294.1820129694302,
+ "y": 1091.3505569214933,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 197,
+ "height": 25,
+ "seed": 435366932,
+ "groupIds": [
+ "24aLAOmaa87ObbblFHJKn",
+ "voMnayuQ28uBsIi7e_G1q",
+ "5rbXSCIsc4jBUQLPW6y0F"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "MUNWjeP_k1E_jhbGobXH1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1669972317976,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "initial validator set",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "initial validator set"
+ },
+ {
+ "type": "text",
+ "version": 1064,
+ "versionNonce": 637034681,
+ "isDeleted": false,
+ "id": "dXpTrrM91wt1Llsj4hQ8b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1114.3070129694302,
+ "y": 336.22555692149325,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 137,
+ "height": 25,
+ "seed": 674507223,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": null,
+ "updated": 1669972214038,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Existing Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Existing Chain"
+ },
+ {
+ "type": "rectangle",
+ "version": 1380,
+ "versionNonce": 89430775,
+ "isDeleted": false,
+ "id": "2lYHI8Dm2Zi6Hk9ZvIlej",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1108.001457413875,
+ "y": 1205.1977791437155,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 424.6111111111107,
+ "height": 151.80555555555597,
+ "seed": 587030647,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": null,
+ "updated": 1669972491130,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "rectangle",
+ "version": 2879,
+ "versionNonce": 1632023063,
+ "isDeleted": false,
+ "id": "q6mbyWjPV8ZYZmccXdKKJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1291.9320129694302,
+ "y": 1245.3366680326044,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 224.25,
+ "height": 92.77777777777789,
+ "seed": 567586969,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": null,
+ "updated": 1669972482164,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 1142,
+ "versionNonce": 1095130841,
+ "isDeleted": false,
+ "id": "P7ErOkLXLLWVc3_EzC8Wl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1325.5570129694302,
+ "y": 1211.2255569214933,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 1759168023,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": null,
+ "updated": 1669972482164,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.png
new file mode 100644
index 00000000..1330a3f5
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-preccv-init-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.excalidraw
new file mode 100644
index 00000000..c52be950
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.excalidraw
@@ -0,0 +1,4367 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 2397,
+ "versionNonce": 829974083,
+ "isDeleted": false,
+ "id": "288dISSfHCk2NOu4muYto",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 424.92702425970924,
+ "y": 1437.7493664453027,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1005.0833333333333,
+ "height": 169.87698412698418,
+ "seed": 1089140909,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600840865,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2115,
+ "versionNonce": 1733232131,
+ "isDeleted": false,
+ "id": "OfGfoX6AzJGTbsVLECmyB",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 423.05202425970924,
+ "y": 621.3743664453026,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1008.8333333333333,
+ "height": 305.0555555555555,
+ "seed": 797809709,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635405,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 913,
+ "versionNonce": 1708840852,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 833.5555555555553,
+ "y": 344.063492063492,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 481,
+ "height": 35,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619523356,
+ "link": null,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "Completion of Unbonding Operations",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Completion of Unbonding Operations"
+ },
+ {
+ "type": "text",
+ "version": 1324,
+ "versionNonce": 462363564,
+ "isDeleted": false,
+ "id": "yqi6dzvpzblq17ysowkTo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 431.16839719235065,
+ "y": 448.03444717631976,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 139,
+ "height": 25,
+ "seed": 578772677,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619523356,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1659,
+ "versionNonce": 2101527828,
+ "isDeleted": false,
+ "id": "Y19fC1uuzabCFyV0s97Ip",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1607.2239527479067,
+ "y": 448.32412971600235,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 159,
+ "height": 25,
+ "seed": 1161289003,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619523356,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chains",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chains"
+ },
+ {
+ "type": "rectangle",
+ "version": 1759,
+ "versionNonce": 625632333,
+ "isDeleted": false,
+ "id": "io7ctwpVbQY3h06icqEIP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 418.9045083034623,
+ "y": 486.804922000858,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1012.5833333333331,
+ "height": 100.05555555555551,
+ "seed": 1043647324,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635406,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2927,
+ "versionNonce": 1983288067,
+ "isDeleted": false,
+ "id": "niGq9B1KH3f2JGEXfsVWR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1082.654508303462,
+ "y": 457.6660331119692,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 326.3333333333336,
+ "height": 1533.0833333333328,
+ "seed": 1857158372,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "WEyZi3lMqkJ8FhlR5xFl1",
+ "type": "arrow"
+ },
+ {
+ "id": "soJdQZVXQySCwfBjVwVtd",
+ "type": "arrow"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635406,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1277,
+ "versionNonce": 477254317,
+ "isDeleted": false,
+ "id": "mKQR2MU2I6RaZxHleDOeo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1181.5711749701286,
+ "y": 420.33269977863563,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 461936988,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635406,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "rectangle",
+ "version": 3293,
+ "versionNonce": 1486043811,
+ "isDeleted": false,
+ "id": "ZBGOYMeC4-MdhYaPJ-0IB",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 580.9045083034619,
+ "y": 456.58269977863574,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 304.33333333333326,
+ "height": 1531.083333333333,
+ "seed": 1937775204,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "WEyZi3lMqkJ8FhlR5xFl1",
+ "type": "arrow"
+ },
+ {
+ "id": "soJdQZVXQySCwfBjVwVtd",
+ "type": "arrow"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ },
+ {
+ "id": "zRzvyJHLJcGNoyUqNi09v",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635406,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1255,
+ "versionNonce": 424506637,
+ "isDeleted": false,
+ "id": "8MWo4aqmMRZ_6M0R-fqTm",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 665.321174970128,
+ "y": 420.24936644530237,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 147,
+ "height": 25,
+ "seed": 401616348,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635406,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Staking Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Staking Module"
+ },
+ {
+ "type": "text",
+ "version": 1854,
+ "versionNonce": 1545651469,
+ "isDeleted": false,
+ "id": "v3aZhl7kBQN_LlwRZKca8",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 656.2882878336204,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 159,
+ "height": 25,
+ "seed": 806156493,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init undelegate-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init undelegate-1"
+ },
+ {
+ "type": "rectangle",
+ "version": 1107,
+ "versionNonce": 117677635,
+ "isDeleted": false,
+ "id": "mjb-z7j9KAQicoyLxGdMD",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 521.3326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 217.00000000000003,
+ "height": 43.75,
+ "seed": 102106061,
+ "groupIds": [
+ "VIocaW0VwoAK-tEMbwF6i"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Ygx86iuovd0TmthLMHcRq",
+ "type": "text"
+ },
+ {
+ "id": "Ygx86iuovd0TmthLMHcRq",
+ "type": "text"
+ },
+ {
+ "id": "Ygx86iuovd0TmthLMHcRq",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "type": "text",
+ "id": "Ygx86iuovd0TmthLMHcRq"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1079,
+ "versionNonce": 322870125,
+ "isDeleted": false,
+ "id": "Ygx86iuovd0TmthLMHcRq",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 611.6064753554905,
+ "y": 530.7076997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 207.00000000000003,
+ "height": 25,
+ "seed": 286698371,
+ "groupIds": [
+ "VIocaW0VwoAK-tEMbwF6i"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "mjb-z7j9KAQicoyLxGdMD",
+ "originalText": "EndBlock"
+ },
+ {
+ "type": "rectangle",
+ "version": 1592,
+ "versionNonce": 1215885933,
+ "isDeleted": false,
+ "id": "bsvuNHc8NDse8ddQcKZVC",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 520.7076997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 282,
+ "height": 44.99999999999999,
+ "seed": 171907533,
+ "groupIds": [
+ "gfLFyjEdxp9zQoBw_3SqN"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "gf9fplNPnrE2fF2kM1scl"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1635,
+ "versionNonce": 1301772003,
+ "isDeleted": false,
+ "id": "gf9fplNPnrE2fF2kM1scl",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1104.9216781417922,
+ "y": 530.7076997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 272,
+ "height": 25,
+ "seed": 271709571,
+ "groupIds": [
+ "gfLFyjEdxp9zQoBw_3SqN"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "bsvuNHc8NDse8ddQcKZVC",
+ "originalText": "EndBlock"
+ },
+ {
+ "type": "text",
+ "version": 1944,
+ "versionNonce": 317332685,
+ "isDeleted": false,
+ "id": "2XMg0yRe96WOesVqiCQQ-",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 656.2882878336204,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 220,
+ "height": 25,
+ "seed": 1796815331,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": ""
+ },
+ {
+ "type": "arrow",
+ "version": 2810,
+ "versionNonce": 239004045,
+ "isDeleted": false,
+ "id": "3SeouFV8muGxqHUkL0Vc8",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 918.1530852701657,
+ "y": 552.5973901947737,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 131.8240708739196,
+ "height": 1.0224628362805106,
+ "seed": 626176963,
+ "groupIds": [
+ "vexHwxQT-Qwn1_fQ4WMZu"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635406,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 131.8240708739196,
+ -1.0224628362805106
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1647,
+ "versionNonce": 682099139,
+ "isDeleted": false,
+ "id": "qdAEsThxT6FSsrS7Kt4pK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 944.8321512501689,
+ "y": 509.81800936249795,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 80,
+ "height": 25,
+ "seed": 355835373,
+ "groupIds": [
+ "vexHwxQT-Qwn1_fQ4WMZu"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635406,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates"
+ },
+ {
+ "type": "arrow",
+ "version": 2888,
+ "versionNonce": 388247533,
+ "isDeleted": false,
+ "id": "JnqmaagVJuwrk_54IKvQ8",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 920.9151937961719,
+ "y": 680.7060229665822,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 129.0619623479132,
+ "height": 0.27011154965987316,
+ "seed": 976668835,
+ "groupIds": [
+ "65A-wNjKwxPx9Z6rx72Qa"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635406,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 129.0619623479132,
+ -0.27011154965987316
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1327,
+ "versionNonce": 379639981,
+ "isDeleted": false,
+ "id": "ZhWLObdAQ7DNXYE3H3QBZ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1461.445910951049,
+ "y": 804.7276573363806,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 119,
+ "height": 25,
+ "seed": 1497949645,
+ "groupIds": [
+ "OBrDlmfGyuZF1MjnQMPCx"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-1"
+ },
+ {
+ "type": "arrow",
+ "version": 2257,
+ "versionNonce": 1123554467,
+ "isDeleted": false,
+ "id": "jjmd9OK2eou689hzpSx2D",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1464.6319416280771,
+ "y": 850.1877422208913,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 121.62386851542169,
+ "height": 1.693510304863139,
+ "seed": 1752885635,
+ "groupIds": [
+ "OBrDlmfGyuZF1MjnQMPCx"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 121.62386851542169,
+ -1.693510304863139
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 1846,
+ "versionNonce": 974970339,
+ "isDeleted": false,
+ "id": "zrerEZvkoppdLgVEr-HAR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 1462.9576997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 253.24999999999997,
+ "height": 124.28571428571442,
+ "seed": 737507619,
+ "groupIds": [
+ "ac8_hZSpXUKuuvN3hcNoN"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "type": "text"
+ },
+ {
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "type": "text"
+ },
+ {
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "type": "text"
+ },
+ {
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1918,
+ "versionNonce": 590162307,
+ "isDeleted": false,
+ "id": "ZiI3_vPGpIcZ3QZEd-jZ-",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 611.6064753554905,
+ "y": 1475.1005569214929,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 243.24999999999997,
+ "height": 100,
+ "seed": 1393843853,
+ "groupIds": [
+ "ac8_hZSpXUKuuvN3hcNoN"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- compute updates-j\n- complete undelegate-1\n- complete redelegate-1",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "zrerEZvkoppdLgVEr-HAR",
+ "originalText": "EndBlock\n- compute updates-j\n- complete undelegate-1\n- complete redelegate-1"
+ },
+ {
+ "type": "arrow",
+ "version": 3017,
+ "versionNonce": 538595693,
+ "isDeleted": false,
+ "id": "s4m9JE4Ws5SNKruUeKciX",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 919.0426513063876,
+ "y": 867.6329216669907,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 130.99912129944914,
+ "height": 0.47416060277919314,
+ "seed": 1145786371,
+ "groupIds": [
+ "yGSu-GF2cmHnrANMW04OV"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 130.99912129944914,
+ 0.47416060277919314
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1841,
+ "versionNonce": 1115654115,
+ "isDeleted": false,
+ "id": "PJ7or8MkoB5Ta3Ijgl2nS",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 937.8967677119203,
+ "y": 826.3501642737743,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 94,
+ "height": 25,
+ "seed": 1960043949,
+ "groupIds": [
+ "yGSu-GF2cmHnrANMW04OV"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-1"
+ },
+ {
+ "type": "rectangle",
+ "version": 1737,
+ "versionNonce": 2112660099,
+ "isDeleted": false,
+ "id": "xUOhnu2c7zi5RcZBJm9_0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 780.9576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 289.49999999999994,
+ "height": 135,
+ "seed": 1594382243,
+ "groupIds": [
+ "t4kjRFeOj_Zv00-6NfpW0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "xJ9MQG4krLBiVC1J-n9Cd"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1779,
+ "versionNonce": 512162605,
+ "isDeleted": false,
+ "id": "xJ9MQG4krLBiVC1J-n9Cd",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1104.9216781417922,
+ "y": 798.4576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 279.49999999999994,
+ "height": 100,
+ "seed": 906274829,
+ "groupIds": [
+ "t4kjRFeOj_Zv00-6NfpW0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- VSC1:={vscId, updates-1}\n- provide VSC1\n- vscId++",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "xUOhnu2c7zi5RcZBJm9_0",
+ "originalText": "EndBlock\n- VSC1:={vscId, updates-1}\n- provide VSC1\n- vscId++"
+ },
+ {
+ "type": "rectangle",
+ "version": 1465,
+ "versionNonce": 1354130477,
+ "isDeleted": false,
+ "id": "FWaH8-7qhZ3CCEqeqDGDE",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 816.9576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 217.00000000000003,
+ "height": 85,
+ "seed": 402877251,
+ "groupIds": [
+ "UTPDgVuWHHgewtd7yku-H"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "type": "text"
+ },
+ {
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "type": "text"
+ },
+ {
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "type": "text"
+ },
+ {
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "QtScPVLIHX_XpCTGNl19E"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1435,
+ "versionNonce": 124977443,
+ "isDeleted": false,
+ "id": "QtScPVLIHX_XpCTGNl19E",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 611.6064753554905,
+ "y": 834.4576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 207,
+ "height": 50,
+ "seed": 1823350381,
+ "groupIds": [
+ "UTPDgVuWHHgewtd7yku-H"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- compute updates-1",
+ "baseline": 43,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "FWaH8-7qhZ3CCEqeqDGDE",
+ "originalText": "EndBlock\n- compute updates-1"
+ },
+ {
+ "type": "diamond",
+ "version": 1564,
+ "versionNonce": 901958797,
+ "isDeleted": false,
+ "id": "8FSwgT1wTNXMVwek31Zcj",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 253.22826628158566,
+ "y": 818.4576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 145,
+ "height": 105,
+ "seed": 328927971,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "b9turHYT8j_iZlkRmUQJd",
+ "type": "text"
+ },
+ {
+ "id": "b9turHYT8j_iZlkRmUQJd",
+ "type": "text"
+ },
+ {
+ "id": "b9turHYT8j_iZlkRmUQJd",
+ "type": "text"
+ },
+ {
+ "id": "-r0GObbpXNWzCa57TLWTv",
+ "type": "arrow"
+ },
+ {
+ "id": "b9turHYT8j_iZlkRmUQJd",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "b9turHYT8j_iZlkRmUQJd"
+ }
+ ],
+ "updated": 1642600635407,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1521,
+ "versionNonce": 369610435,
+ "isDeleted": false,
+ "id": "b9turHYT8j_iZlkRmUQJd",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 258.22826628158566,
+ "y": 858.4576997786359,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 135,
+ "height": 25,
+ "seed": 1335478477,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Tendermint",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "8FSwgT1wTNXMVwek31Zcj",
+ "originalText": "Tendermint"
+ },
+ {
+ "type": "arrow",
+ "version": 3988,
+ "versionNonce": 171160301,
+ "isDeleted": false,
+ "id": "tRsPeghC6Larxxo1-cOHT",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 548.8499010357573,
+ "y": 872.4379527210932,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 117.80814515172608,
+ "height": 1.2723534635447322,
+ "seed": 1717417603,
+ "groupIds": [
+ "QwYvAiHAt3q-R51TmsfOS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -117.80814515172608,
+ 1.2723534635447322
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2322,
+ "versionNonce": 709295715,
+ "isDeleted": false,
+ "id": "isS-yd5bGYUz8MEWFM5RH",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 441.0476489368832,
+ "y": 828.3501642737748,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 94,
+ "height": 25,
+ "seed": 6367021,
+ "groupIds": [
+ "QwYvAiHAt3q-R51TmsfOS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-1"
+ },
+ {
+ "type": "text",
+ "version": 1406,
+ "versionNonce": 1570798380,
+ "isDeleted": false,
+ "id": "SzCr9Evju7A0-EI2hAVan",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1418.2446702401855,
+ "y": 1304.2543789965198,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 212,
+ "height": 25,
+ "seed": 800354189,
+ "groupIds": [
+ "nKurdQH5Lw4OmdxDJxyCg"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ }
+ ],
+ "updated": 1645619661897,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCMaturedPackets-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCMaturedPackets-1"
+ },
+ {
+ "type": "arrow",
+ "version": 2228,
+ "versionNonce": 655957396,
+ "isDeleted": false,
+ "id": "qxQQDOIb_6yBAx_VEeb37",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1583.4002884726644,
+ "y": 1348.8511166031087,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 123.56280714681475,
+ "height": 0.8260181955356529,
+ "seed": 666229699,
+ "groupIds": [
+ "nKurdQH5Lw4OmdxDJxyCg"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619661897,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -123.56280714681475,
+ 0.8260181955356529
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 855,
+ "versionNonce": 2036282595,
+ "isDeleted": false,
+ "id": "dHBZb2RdXqXsUyWx3KiOq",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 467.468690926376,
+ "y": 527.1521442230803,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 52,
+ "height": 25,
+ "seed": 135797549,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block"
+ },
+ {
+ "type": "rectangle",
+ "version": 2124,
+ "versionNonce": 369926275,
+ "isDeleted": false,
+ "id": "kdNbiNehK3QVI1DL8BGYt",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 425.5275274313726,
+ "y": 961.9993664453024,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1006.3333333333335,
+ "height": 246.30555555555554,
+ "seed": 618253869,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "M5sZD3idFu959njYfEyeu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635407,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1869,
+ "versionNonce": 504583821,
+ "isDeleted": false,
+ "id": "mhqdDomzdSXgWXV-WcaDJ",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 997.1632878336203,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 168,
+ "height": 25,
+ "seed": 152254243,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init undelegate-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init undelegate-2"
+ },
+ {
+ "type": "text",
+ "version": 1965,
+ "versionNonce": 769350179,
+ "isDeleted": false,
+ "id": "phK7bzOTf8HJPVnfLFghy",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 997.1632878336203,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 229,
+ "height": 25,
+ "seed": 775520397,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": ""
+ },
+ {
+ "type": "arrow",
+ "version": 2971,
+ "versionNonce": 335434900,
+ "isDeleted": false,
+ "id": "3NrrQafWyNKIVHetasLgc",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 918.2121255392635,
+ "y": 1006.0427804953314,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 130.31196234791332,
+ "height": 0.2711443590825411,
+ "seed": 210131651,
+ "groupIds": [
+ "cB6bFsaKlgxurLpKAbJKw"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619787666,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 130.31196234791332,
+ -0.2711443590825411
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1411,
+ "versionNonce": 592459245,
+ "isDeleted": false,
+ "id": "PLhcueSSD57kWN6Qi-0hy",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1461.445910951049,
+ "y": 1086.6026573363806,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 25,
+ "seed": 1762921059,
+ "groupIds": [
+ "E8OZCCeBcSVKKnXG7XX_J"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-2"
+ },
+ {
+ "type": "arrow",
+ "version": 2336,
+ "versionNonce": 560474979,
+ "isDeleted": false,
+ "id": "hmUXP0GIWwIo13Y3ulP7V",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1468.6319416280767,
+ "y": 1132.0627422208913,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 121.62386851542169,
+ "height": 1.693510304863139,
+ "seed": 1775403341,
+ "groupIds": [
+ "E8OZCCeBcSVKKnXG7XX_J"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 121.62386851542169,
+ -1.693510304863139
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 3146,
+ "versionNonce": 688713805,
+ "isDeleted": false,
+ "id": "jMOzMGNmj6p2YM8NwCj4-",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 920.9214990138784,
+ "y": 1149.093477787134,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 127.59577676362153,
+ "height": 0.3613955173641443,
+ "seed": 864732675,
+ "groupIds": [
+ "STwyOVqEMrli0DcBTproc"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 127.59577676362153,
+ -0.3613955173641443
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1942,
+ "versionNonce": 1857210115,
+ "isDeleted": false,
+ "id": "KyCMqbrlIZV3ujG0FLBh0",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 931.8722708835835,
+ "y": 1106.9751642737742,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 103,
+ "height": 25,
+ "seed": 871288749,
+ "groupIds": [
+ "STwyOVqEMrli0DcBTproc"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635407,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-2"
+ },
+ {
+ "type": "rectangle",
+ "version": 1822,
+ "versionNonce": 867765645,
+ "isDeleted": false,
+ "id": "RB0NFxq5enBSn_1g0Rs1M",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 1062.8326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 292,
+ "height": 135,
+ "seed": 1659440547,
+ "groupIds": [
+ "1Ml5oLQaWc-g3mG6AtEjs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "zwf74_KbAfyIn5CmMaEpU"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1870,
+ "versionNonce": 1246895555,
+ "isDeleted": false,
+ "id": "zwf74_KbAfyIn5CmMaEpU",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1104.9216781417922,
+ "y": 1080.3326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 282,
+ "height": 100,
+ "seed": 1714573837,
+ "groupIds": [
+ "1Ml5oLQaWc-g3mG6AtEjs"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- VSC2:={vscId, updates-2}\n- provide VSC2\n- vscId++",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "RB0NFxq5enBSn_1g0Rs1M",
+ "originalText": "EndBlock\n- VSC2:={vscId, updates-2}\n- provide VSC2\n- vscId++"
+ },
+ {
+ "type": "rectangle",
+ "version": 1549,
+ "versionNonce": 6087875,
+ "isDeleted": false,
+ "id": "BWRQRfnJRP2bLO56TV_Xk",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 1098.8326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 217.00000000000003,
+ "height": 85,
+ "seed": 173891907,
+ "groupIds": [
+ "54R2JrsB_ogK-ZKI5BQYv"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "6n_oBuNMDsal_ABzyahFz"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1520,
+ "versionNonce": 1810037997,
+ "isDeleted": false,
+ "id": "6n_oBuNMDsal_ABzyahFz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 611.6064753554905,
+ "y": 1116.3326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 207,
+ "height": 75,
+ "seed": 1570146413,
+ "groupIds": [
+ "54R2JrsB_ogK-ZKI5BQYv"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- compute updates-2",
+ "baseline": 68,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "BWRQRfnJRP2bLO56TV_Xk",
+ "originalText": "EndBlock\n- compute updates-2"
+ },
+ {
+ "type": "diamond",
+ "version": 1648,
+ "versionNonce": 722687853,
+ "isDeleted": false,
+ "id": "JopS_hj-ZXS5fwF8ERp6u",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 253.20376945324915,
+ "y": 1100.3326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 145,
+ "height": 105,
+ "seed": 1397113059,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "type": "text"
+ },
+ {
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "type": "text"
+ },
+ {
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "type": "text"
+ },
+ {
+ "id": "-r0GObbpXNWzCa57TLWTv",
+ "type": "arrow"
+ },
+ {
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "type": "text"
+ },
+ {
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "MzLtWftrAv0s7667yRuHn"
+ }
+ ],
+ "updated": 1642600635408,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1604,
+ "versionNonce": 1140974051,
+ "isDeleted": false,
+ "id": "MzLtWftrAv0s7667yRuHn",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 258.20376945324915,
+ "y": 1140.3326997786357,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 135,
+ "height": 25,
+ "seed": 467989197,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Tendermint",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "JopS_hj-ZXS5fwF8ERp6u",
+ "originalText": "Tendermint"
+ },
+ {
+ "type": "arrow",
+ "version": 4224,
+ "versionNonce": 1592779213,
+ "isDeleted": false,
+ "id": "U1GKh2Ic0h0HFt8GEtuaT",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 547.8840752672525,
+ "y": 1151.2236818704496,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 115.6168162115581,
+ "height": 0.6116243141882478,
+ "seed": 926035075,
+ "groupIds": [
+ "ItvOetjnwy_YHQhWlMVXp"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -115.6168162115581,
+ 0.6116243141882478
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2427,
+ "versionNonce": 835825027,
+ "isDeleted": false,
+ "id": "zBOyUTJljULnw2kI-Qufe",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 437.77315210854516,
+ "y": 1106.4751642737747,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 103,
+ "height": 25,
+ "seed": 766972205,
+ "groupIds": [
+ "ItvOetjnwy_YHQhWlMVXp"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-2"
+ },
+ {
+ "type": "arrow",
+ "version": 4638,
+ "versionNonce": 1399606211,
+ "isDeleted": false,
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "dashed",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 6.277329793107594,
+ "x": 596.0928834941102,
+ "y": 927.8334623501592,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 2.9100524049084697,
+ "height": 507.68956239314696,
+ "seed": 134041987,
+ "groupIds": [
+ "CqF-dAVHfY77Yn6VmbWfc"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600877592,
+ "link": null,
+ "startBinding": {
+ "elementId": "OfGfoX6AzJGTbsVLECmyB",
+ "focus": 0.6599084118084031,
+ "gap": 1.3993722528743433
+ },
+ "endBinding": {
+ "elementId": "zrerEZvkoppdLgVEr-HAR",
+ "focus": -1.094118517194332,
+ "gap": 27.43050693890325
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "bar",
+ "endArrowhead": "bar",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -2.9100524049084697,
+ 507.68956239314696
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2004,
+ "versionNonce": 28235597,
+ "isDeleted": false,
+ "id": "dlq-LCddvyv9rW9vWOnen",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 1282.6521442230803,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 86,
+ "height": 50,
+ "seed": 1619373805,
+ "groupIds": [
+ "AxP1pTUQVIrrnNNvwelgT"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "unbonding\nperiod",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "unbonding\nperiod"
+ },
+ {
+ "type": "text",
+ "version": 2010,
+ "versionNonce": 1371132909,
+ "isDeleted": false,
+ "id": "JE7v3fw6Lo-vnwABTfGUW",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 1298.1521442230805,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 240,
+ "height": 50,
+ "seed": 1500007565,
+ "groupIds": [
+ "SsR2cRbMRkeYQVfLbQQFK"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "register VSC1 maturity\nfrom all consumer chains",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "register VSC1 maturity\nfrom all consumer chains"
+ },
+ {
+ "type": "arrow",
+ "version": 3361,
+ "versionNonce": 1026647053,
+ "isDeleted": false,
+ "id": "epRmOkOvZWfXiLqu0Qlge",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1049.1877097524193,
+ "y": 1308.7643114112332,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 126.96037696966164,
+ "height": 0.24647998429350082,
+ "seed": 773091331,
+ "groupIds": [
+ "f4ho76RQ9mWQGX32Ab5Md"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -126.96037696966164,
+ 0.24647998429350082
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2312,
+ "versionNonce": 1947234115,
+ "isDeleted": false,
+ "id": "gASwr2kKuNorBYPW6PB7B",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 933.8546672064161,
+ "y": 1241.7430748057857,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 118,
+ "height": 50,
+ "seed": 1068287405,
+ "groupIds": [
+ "f4ho76RQ9mWQGX32Ab5Md"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-1\nmatured",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-1\nmatured"
+ },
+ {
+ "type": "arrow",
+ "version": 3101,
+ "versionNonce": 1726175853,
+ "isDeleted": false,
+ "id": "Qaof0appxyTfGqOyPimeM",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 921.6877097524193,
+ "y": 1540.6646169450064,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 128.06196234791332,
+ "height": 0.2680274478566389,
+ "seed": 744054659,
+ "groupIds": [
+ "trbBqTUQZ8hVyEHwhsPIS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 128.06196234791332,
+ -0.2680274478566389
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2022,
+ "versionNonce": 1869057763,
+ "isDeleted": false,
+ "id": "pWe_TqLEWak9ZMs-2H7zz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 937.1046672064163,
+ "y": 1498.6396715011538,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 95,
+ "height": 25,
+ "seed": 1745915437,
+ "groupIds": [
+ "trbBqTUQZ8hVyEHwhsPIS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-j",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-j"
+ },
+ {
+ "type": "diamond",
+ "version": 1776,
+ "versionNonce": 2047568397,
+ "isDeleted": false,
+ "id": "CxowGLLYEeGhRaFOmUk4P",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 251.5328735492903,
+ "y": 1507.15214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 145,
+ "height": 105,
+ "seed": 510627181,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "id": "-r0GObbpXNWzCa57TLWTv",
+ "type": "arrow"
+ },
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "wUVhER-pspmKNFqCwFdDW"
+ }
+ ],
+ "updated": 1642601376438,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1731,
+ "versionNonce": 239192387,
+ "isDeleted": false,
+ "id": "wUVhER-pspmKNFqCwFdDW",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 256.5328735492903,
+ "y": 1547.15214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 135,
+ "height": 25,
+ "seed": 1814825955,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601376438,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Tendermint",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "CxowGLLYEeGhRaFOmUk4P",
+ "originalText": "Tendermint"
+ },
+ {
+ "type": "arrow",
+ "version": 4199,
+ "versionNonce": 2046117219,
+ "isDeleted": false,
+ "id": "3ImxJI5H1IhgvS85qKlh1",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 548.4045083034616,
+ "y": 1560.3823971655374,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 117.80814515172608,
+ "height": 1.2723534635447322,
+ "seed": 1163220941,
+ "groupIds": [
+ "LrWRbUyeQLT9OV6pnT1k8"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601371578,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -117.80814515172608,
+ 1.2723534635447322
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2558,
+ "versionNonce": 1056339533,
+ "isDeleted": false,
+ "id": "6GQox3CC4___t_Car9og8",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 440.1022562045864,
+ "y": 1516.294608718219,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 95,
+ "height": 25,
+ "seed": 1730582403,
+ "groupIds": [
+ "LrWRbUyeQLT9OV6pnT1k8"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601371578,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-j",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-j"
+ },
+ {
+ "type": "text",
+ "version": 1926,
+ "versionNonce": 1455869955,
+ "isDeleted": false,
+ "id": "n_apeqqeSuAXb8Aa_VqFx",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 733.2067914294267,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 158,
+ "height": 25,
+ "seed": 785185293,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "init redelegate-1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "init redelegate-1"
+ },
+ {
+ "type": "text",
+ "version": 2005,
+ "versionNonce": 510618979,
+ "isDeleted": false,
+ "id": "yuH8tjvqZiQpXEO6g4Dfb",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 733.2067914294267,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 219,
+ "height": 25,
+ "seed": 740374851,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": ""
+ },
+ {
+ "type": "arrow",
+ "version": 2947,
+ "versionNonce": 2036279444,
+ "isDeleted": false,
+ "id": "M5qfHpuAKGd2joPo_LidE",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 921.4377097524193,
+ "y": 747.6245265623885,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 129.0619623479132,
+ "height": 0.27011154965987316,
+ "seed": 211562605,
+ "groupIds": [
+ "52U-6lK1W6brXq10aCVDA"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619775590,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 129.0619623479132,
+ -0.27011154965987316
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 3485,
+ "versionNonce": 306372867,
+ "isDeleted": false,
+ "id": "0v7hh7uxixdfVpYjaX0IN",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1047.8654006842082,
+ "y": 1404.7895225336572,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 126.96037696966164,
+ "height": 0.24647998429350082,
+ "seed": 1121770893,
+ "groupIds": [
+ "bzgS7UIMLtmHiz7NOxjpS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -126.96037696966164,
+ 0.24647998429350082
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2446,
+ "versionNonce": 1352262829,
+ "isDeleted": false,
+ "id": "2gQDdpyI5F1fliwPoB914",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 933.0323581382052,
+ "y": 1337.7682859282097,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 117,
+ "height": 50,
+ "seed": 202591683,
+ "groupIds": [
+ "bzgS7UIMLtmHiz7NOxjpS"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "redelegate-1\nmatured",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "redelegate-1\nmatured"
+ },
+ {
+ "type": "rectangle",
+ "version": 1652,
+ "versionNonce": 1418907213,
+ "isDeleted": false,
+ "id": "-L4r_-m6RbrGgavC67Rjk",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 1526.40214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 282,
+ "height": 44.99999999999999,
+ "seed": 101774467,
+ "groupIds": [
+ "BIN_XiQ90fjv_AvqHTY2r"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1694,
+ "versionNonce": 226609411,
+ "isDeleted": false,
+ "id": "Xsb8ZrPkxJ7fF3p7A30aR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1104.9216781417922,
+ "y": 1536.40214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 272,
+ "height": 25,
+ "seed": 1460193581,
+ "groupIds": [
+ "BIN_XiQ90fjv_AvqHTY2r"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "-L4r_-m6RbrGgavC67Rjk",
+ "originalText": "EndBlock"
+ },
+ {
+ "type": "arrow",
+ "version": 4832,
+ "versionNonce": 541089133,
+ "isDeleted": false,
+ "id": "M5sZD3idFu959njYfEyeu",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "dashed",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 6.277329793107594,
+ "x": 876.2468575020056,
+ "y": 1214.4108870905281,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 0.04088500122713867,
+ "height": 446.6102100653302,
+ "seed": 1822069859,
+ "groupIds": [
+ "F-MSJHdtVFQVUvZCVH0mb"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635408,
+ "link": null,
+ "startBinding": {
+ "elementId": "kdNbiNehK3QVI1DL8BGYt",
+ "focus": 0.10816135727922097,
+ "gap": 6.109673614163796
+ },
+ "endBinding": {
+ "elementId": "QXlbO191aqO2Ebca6nJSm",
+ "focus": -0.09325154026316189,
+ "gap": 4.731977813937419
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "bar",
+ "endArrowhead": "bar",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.04088500122713867,
+ 446.6102100653302
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2094,
+ "versionNonce": 2010451693,
+ "isDeleted": false,
+ "id": "BsxGg6VO6rGscqezmTejP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 770.8921896412048,
+ "y": 1367.6521442230799,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 86,
+ "height": 50,
+ "seed": 1038528611,
+ "groupIds": [
+ "_3gCHPt_YFY_m1npu2XJ5"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600882925,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "unbonding\nperiod",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "unbonding\nperiod"
+ },
+ {
+ "type": "rectangle",
+ "version": 2490,
+ "versionNonce": 126142339,
+ "isDeleted": false,
+ "id": "QXlbO191aqO2Ebca6nJSm",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 422.37409961491886,
+ "y": 1665.7493664453023,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 1005.0833333333333,
+ "height": 162.5555555555557,
+ "seed": 27175747,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "jxiIfnYuV__VAbb-JgmY7",
+ "type": "arrow"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "id": "M5sZD3idFu959njYfEyeu",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600635408,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 1941,
+ "versionNonce": 370399139,
+ "isDeleted": false,
+ "id": "mpRpGxyyho1lobPdtJ75y",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 1690.9576997786353,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 253.24999999999997,
+ "height": 117.50000000000001,
+ "seed": 1835458157,
+ "groupIds": [
+ "JrgL8elQW7Dq3Q6PWzPuU"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "type": "text"
+ },
+ {
+ "id": "cFz81tlJ2NMZiYzWVMuf6",
+ "type": "arrow"
+ },
+ {
+ "type": "text",
+ "id": "pIw5JMoColGr9m_qovMhF"
+ }
+ ],
+ "updated": 1642600868111,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 2042,
+ "versionNonce": 1735469069,
+ "isDeleted": false,
+ "id": "pIw5JMoColGr9m_qovMhF",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 611.6064753554905,
+ "y": 1699.7076997786353,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 243.24999999999997,
+ "height": 100,
+ "seed": 667831011,
+ "groupIds": [
+ "JrgL8elQW7Dq3Q6PWzPuU"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock\n- compute updates-k\n- ! cannot complete \nundelegate-2",
+ "baseline": 93,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "mpRpGxyyho1lobPdtJ75y",
+ "originalText": "EndBlock\n- compute updates-k\n- ! cannot complete undelegate-2"
+ },
+ {
+ "type": "arrow",
+ "version": 3196,
+ "versionNonce": 1452562573,
+ "isDeleted": false,
+ "id": "UC5ZzJxhmHYCs7KEr8mvo",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 919.134785107629,
+ "y": 1768.664616945006,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 128.06196234791332,
+ "height": 0.2680274478566389,
+ "seed": 993691853,
+ "groupIds": [
+ "GK0iuN3yOAXSK5NbTvgTY"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 128.06196234791332,
+ -0.2680274478566389
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2119,
+ "versionNonce": 1306854083,
+ "isDeleted": false,
+ "id": "73rDFdHpWyiGnIX69Rmqj",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 933.0517425616258,
+ "y": 1726.6396715011533,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 98,
+ "height": 25,
+ "seed": 304674435,
+ "groupIds": [
+ "GK0iuN3yOAXSK5NbTvgTY"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-k",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-k"
+ },
+ {
+ "type": "diamond",
+ "version": 1876,
+ "versionNonce": 1474429581,
+ "isDeleted": false,
+ "id": "_ebS12dRowA_tQI8j45CY",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 248.9799489044999,
+ "y": 1738.1521442230796,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 145,
+ "height": 105,
+ "seed": 971630381,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "-r0GObbpXNWzCa57TLWTv",
+ "type": "arrow"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "E673Ak76SUOSt77Qi8bsE"
+ }
+ ],
+ "updated": 1642601420357,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1830,
+ "versionNonce": 287242435,
+ "isDeleted": false,
+ "id": "E673Ak76SUOSt77Qi8bsE",
+ "fillStyle": "solid",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 253.9799489044999,
+ "y": 1778.1521442230796,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 135,
+ "height": 25,
+ "seed": 527248931,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601420358,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Tendermint",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "middle",
+ "containerId": "_ebS12dRowA_tQI8j45CY",
+ "originalText": "Tendermint"
+ },
+ {
+ "type": "arrow",
+ "version": 4294,
+ "versionNonce": 1325148397,
+ "isDeleted": false,
+ "id": "jRnLv9fNuoznNAaRsNZgk",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 545.8515836586712,
+ "y": 1788.382397165537,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 117.80814515172608,
+ "height": 1.2723534635447322,
+ "seed": 1004435853,
+ "groupIds": [
+ "YrqVjtQGzKIAhQyl39Kp9"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601420358,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -117.80814515172608,
+ 1.2723534635447322
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2655,
+ "versionNonce": 1290332259,
+ "isDeleted": false,
+ "id": "wB_7P6AInvDavIoMbkl8E",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 436.04933155979603,
+ "y": 1744.2946087182186,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 98,
+ "height": 25,
+ "seed": 1384739267,
+ "groupIds": [
+ "YrqVjtQGzKIAhQyl39Kp9"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601420358,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "updates-k",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "updates-k"
+ },
+ {
+ "type": "rectangle",
+ "version": 1749,
+ "versionNonce": 1528659117,
+ "isDeleted": false,
+ "id": "jFpSjGFb3cu0a0QbNucLK",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 1754.4021442230796,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 282,
+ "height": 44.99999999999999,
+ "seed": 173129709,
+ "groupIds": [
+ "6OEOlibeQD_u8MFuWagZW"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "9c_Yl7A91iXAKKFtnu4tQ",
+ "type": "arrow"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "wiNk7YxLC6w3qcdxfA5HM",
+ "type": "arrow"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "type": "text"
+ },
+ {
+ "type": "text",
+ "id": "XTAVne28eQ084ExAjqSpY"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1790,
+ "versionNonce": 218607779,
+ "isDeleted": false,
+ "id": "XTAVne28eQ084ExAjqSpY",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1104.9216781417922,
+ "y": 1764.4021442230796,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 272,
+ "height": 25,
+ "seed": 1285308771,
+ "groupIds": [
+ "6OEOlibeQD_u8MFuWagZW"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "EndBlock",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": "jFpSjGFb3cu0a0QbNucLK",
+ "originalText": "EndBlock"
+ },
+ {
+ "type": "text",
+ "version": 2080,
+ "versionNonce": 1828937485,
+ "isDeleted": false,
+ "id": "QSiOaQm7YoHL3FHb5xGQR",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1099.9216781417922,
+ "y": 1888.90214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 240,
+ "height": 50,
+ "seed": 239799565,
+ "groupIds": [
+ "DpRuLZsRaxufqGOHn5Hhe"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642600919620,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "register VSC2 maturity\nfrom all consumer chains",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "register VSC2 maturity\nfrom all consumer chains"
+ },
+ {
+ "type": "arrow",
+ "version": 3429,
+ "versionNonce": 460510339,
+ "isDeleted": false,
+ "id": "S32vBZg5ju-5zAgb6D7qY",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1047.8654006842082,
+ "y": 1937.8097612767697,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 126.96037696966164,
+ "height": 0.24647998429350082,
+ "seed": 1358265827,
+ "groupIds": [
+ "O0RfEkM6R98ddCqKabsm1"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -126.96037696966164,
+ 0.24647998429350082
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 2382,
+ "versionNonce": 691333421,
+ "isDeleted": false,
+ "id": "NJ3kaRH3ykxaEPbuL6kwI",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 928.0323581382052,
+ "y": 1870.7885246713222,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 127,
+ "height": 50,
+ "seed": 26478029,
+ "groupIds": [
+ "O0RfEkM6R98ddCqKabsm1"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "undelegate-2\nmatured",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "undelegate-2\nmatured"
+ },
+ {
+ "type": "text",
+ "version": 81,
+ "versionNonce": 1637804867,
+ "isDeleted": false,
+ "id": "FlycApHxvNhc7EzTtDfVw",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "dashed",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 606.6064753554905,
+ "y": 1913.90214422308,
+ "strokeColor": "#000000",
+ "backgroundColor": "#fa5252",
+ "width": 220,
+ "height": 25,
+ "seed": 334653923,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600868111,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "complete undelegate-2",
+ "baseline": 18,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "complete undelegate-2"
+ },
+ {
+ "type": "text",
+ "version": 1473,
+ "versionNonce": 1078215149,
+ "isDeleted": false,
+ "id": "4uwRrBf63CyHdZgytHPgP",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1461.445910951049,
+ "y": 1508.7613874951103,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 120,
+ "height": 25,
+ "seed": 1561641411,
+ "groupIds": [
+ "v10k9qiCW2ZlkFRAoU0v7"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-j",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-j"
+ },
+ {
+ "type": "arrow",
+ "version": 2396,
+ "versionNonce": 109764451,
+ "isDeleted": false,
+ "id": "LAJit7p8fwSjpli354rmQ",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1465.1319416280767,
+ "y": 1554.2214723796214,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 121.62386851542169,
+ "height": 1.693510304863139,
+ "seed": 1021538285,
+ "groupIds": [
+ "v10k9qiCW2ZlkFRAoU0v7"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 121.62386851542169,
+ -1.693510304863139
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1528,
+ "versionNonce": 597654605,
+ "isDeleted": false,
+ "id": "LcE9oP3jo0SV72kN6WVl7",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1461.445910951049,
+ "y": 1735.9042446379674,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 123,
+ "height": 25,
+ "seed": 2051265795,
+ "groupIds": [
+ "bCkx_NwLyCOUyNPR2c83i"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket-k",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket-k"
+ },
+ {
+ "type": "arrow",
+ "version": 2449,
+ "versionNonce": 90110723,
+ "isDeleted": false,
+ "id": "aiXbgSJSQTjiViNaUGmMf",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1466.6319416280767,
+ "y": 1781.3643295224786,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 121.62386851542169,
+ "height": 1.693510304863139,
+ "seed": 475148461,
+ "groupIds": [
+ "bCkx_NwLyCOUyNPR2c83i"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642600635409,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 121.62386851542169,
+ -1.693510304863139
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 1951,
+ "versionNonce": 1359653507,
+ "isDeleted": false,
+ "id": "lWhZF3KHnueXQn1g7preT",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1632.307976640662,
+ "y": 488.6342870802232,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 22.857142857142406,
+ "height": 1489.2857142857147,
+ "seed": 1833606115,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "HHI5Vc7fqPn6VuRzXoIaY",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642601108975,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 1364,
+ "versionNonce": 450358979,
+ "isDeleted": false,
+ "id": "lvwKtrmcLxqDV3LzwuTSz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1472.9030270444368,
+ "y": 511.61853035225386,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 105,
+ "height": 25,
+ "seed": 771374435,
+ "groupIds": [
+ "Qi942Aq3vXaCC_NBM6xKv"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642601084994,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket"
+ },
+ {
+ "type": "arrow",
+ "version": 2292,
+ "versionNonce": 1465624547,
+ "isDeleted": false,
+ "id": "5zz8ESfQcxy3owCiP9VT3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1469.089057721465,
+ "y": 557.0786152367646,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 121.62386851542169,
+ "height": 1.693510304863139,
+ "seed": 1943862861,
+ "groupIds": [
+ "Qi942Aq3vXaCC_NBM6xKv"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1642601080289,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 121.62386851542169,
+ -1.693510304863139
+ ]
+ ]
+ },
+ {
+ "type": "rectangle",
+ "version": 2071,
+ "versionNonce": 439186221,
+ "isDeleted": false,
+ "id": "Ov7Nt-iR2V6wD_0fP-mUY",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1672.593690926376,
+ "y": 488.6342870802232,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 22.857142857142406,
+ "height": 1489.2857142857147,
+ "seed": 692385091,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "HHI5Vc7fqPn6VuRzXoIaY",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642601108975,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2044,
+ "versionNonce": 2146984483,
+ "isDeleted": false,
+ "id": "zDLZUtrO8Lij6Z4ctMo67",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1711.1651194978049,
+ "y": 488.6342870802231,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 22.857142857142406,
+ "height": 1489.2857142857147,
+ "seed": 1478186211,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "HHI5Vc7fqPn6VuRzXoIaY",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1642601108975,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 933,
+ "versionNonce": 257253005,
+ "isDeleted": false,
+ "id": "BF8omtpN5J817AOrrJinc",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 465.5222623549474,
+ "y": 637.8485727945094,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 67,
+ "height": 25,
+ "seed": 156612141,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642601398863,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block 1",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block 1"
+ },
+ {
+ "type": "text",
+ "version": 904,
+ "versionNonce": 1034784707,
+ "isDeleted": false,
+ "id": "EXAbJSyrRTJfdeB6ZeE9e",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 461.7365480692332,
+ "y": 978.8485727945097,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 76,
+ "height": 25,
+ "seed": 284738093,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642601384686,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block 2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block 2"
+ },
+ {
+ "type": "text",
+ "version": 944,
+ "versionNonce": 186422403,
+ "isDeleted": false,
+ "id": "EeggQ3YD-incj8Aa12fst",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 458.5936909263761,
+ "y": 1453.4200013659383,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 68,
+ "height": 25,
+ "seed": 1671725389,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642601379543,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block j",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block j"
+ },
+ {
+ "type": "text",
+ "version": 992,
+ "versionNonce": 268758787,
+ "isDeleted": false,
+ "id": "dmYqu29T9lOTUDFFjP5dR",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 464.2365480692332,
+ "y": 1683.2771442230812,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 71,
+ "height": 25,
+ "seed": 2007237869,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1642601409906,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Block k",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Block k"
+ },
+ {
+ "type": "text",
+ "version": 1455,
+ "versionNonce": 1639310996,
+ "isDeleted": false,
+ "id": "neyeN0wmFK2sHMHSi1Cyv",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1412.3112543352074,
+ "y": 1885.6802121324004,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 221,
+ "height": 25,
+ "seed": 1220556204,
+ "groupIds": [
+ "f6V1v01vZdzSr0UWMa1V0"
+ ],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ }
+ ],
+ "updated": 1645619672989,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCMaturedPackets-2",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCMaturedPackets-2"
+ },
+ {
+ "type": "arrow",
+ "version": 2274,
+ "versionNonce": 703626412,
+ "isDeleted": false,
+ "id": "NWLK_Lfb3vSSeX03apdO3",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1581.9668725676863,
+ "y": 1930.276949738989,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 123.56280714681475,
+ "height": 0.8260181955356529,
+ "seed": 1254640404,
+ "groupIds": [
+ "f6V1v01vZdzSr0UWMa1V0"
+ ],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619672989,
+ "link": null,
+ "startBinding": null,
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -123.56280714681475,
+ 0.8260181955356529
+ ]
+ ]
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.png
new file mode 100644
index 00000000..30455eb8
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-unbonding-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.excalidraw b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.excalidraw
new file mode 100644
index 00000000..1eae53f8
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.excalidraw
@@ -0,0 +1,946 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 844,
+ "versionNonce": 725100588,
+ "isDeleted": false,
+ "id": "Lzfre0sNq-j_kaO2bid-V",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 1033.80728608124,
+ "y": 370.7566693985417,
+ "strokeColor": "#e67700",
+ "backgroundColor": "#e67700",
+ "width": 236.99999999999977,
+ "height": 425.0000000000002,
+ "seed": 875787883,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "umXKeqaL3A2gsELJxrAno",
+ "type": "arrow"
+ },
+ {
+ "id": "JdK3ZV05Kgdm0t1Wbwel3",
+ "type": "arrow"
+ },
+ {
+ "id": "Hg015OniyGFjM7QYWPufU",
+ "type": "arrow"
+ },
+ {
+ "id": "1qXvD7xEgjTf4VUM5wDV3",
+ "type": "arrow"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130251,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 822,
+ "versionNonce": 1641988244,
+ "isDeleted": false,
+ "id": "tiEtkt2XHCbZp9ZIwwoVN",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 20,
+ "angle": 0,
+ "x": 631.0850638590175,
+ "y": 364.8677805096529,
+ "strokeColor": "#1864ab",
+ "backgroundColor": "#1864ab",
+ "width": 221.33333333333323,
+ "height": 430.55555555555543,
+ "seed": 1114932363,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "DURyamuWvIhE5o-GLqnvp",
+ "type": "arrow"
+ },
+ {
+ "id": "-sQ1lqt81ejZKXt6Q2HFf",
+ "type": "arrow"
+ },
+ {
+ "id": "gMMtc7H_hFXaSquI_JbUz",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 912,
+ "versionNonce": 1088045740,
+ "isDeleted": false,
+ "id": "DcYomqVwJx0rUjMvf5S5b",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 780.5555555555554,
+ "y": 266.77777777777777,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 391,
+ "height": 36,
+ "seed": 484186313,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 28,
+ "fontFamily": 1,
+ "text": "CCV - Validator Set Update",
+ "baseline": 25,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV - Validator Set Update"
+ },
+ {
+ "type": "rectangle",
+ "version": 2333,
+ "versionNonce": 48336404,
+ "isDeleted": false,
+ "id": "LZ-zpiSZrRKjK21SSB5me",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 664.6961749701286,
+ "y": 420.36778050965296,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 154.3333333333333,
+ "height": 343.3333333333332,
+ "seed": 689622821,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "WEyZi3lMqkJ8FhlR5xFl1",
+ "type": "arrow"
+ },
+ {
+ "id": "soJdQZVXQySCwfBjVwVtd",
+ "type": "arrow"
+ },
+ {
+ "id": "pFUtKITgaRpYy9le7pyUv",
+ "type": "arrow"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null
+ },
+ {
+ "type": "rectangle",
+ "version": 2383,
+ "versionNonce": 1079315756,
+ "isDeleted": false,
+ "id": "bV7tck2JEoFVq7rV8HSWu",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1070.9739527479064,
+ "y": 426.03444717631953,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 172.33333333333331,
+ "height": 344.4444444444449,
+ "seed": 1430831243,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "gdGzxModJaJKV9kGsQ0lN"
+ },
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "type": "arrow",
+ "id": "L-m2OjIz_DM9FZL_aw8q1"
+ },
+ {
+ "type": "arrow",
+ "id": "MUNWjeP_k1E_jhbGobXH1"
+ },
+ {
+ "type": "arrow",
+ "id": "Z366__sEfBTh-UkidbmWs"
+ },
+ {
+ "id": "L-SUQMkaBFUSpHYaSKHI8",
+ "type": "arrow"
+ },
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "jz87Jfblhql4prXUNauec",
+ "type": "arrow"
+ },
+ {
+ "id": "XqKtKE8gPBp_eAGHN-Ucc",
+ "type": "arrow"
+ },
+ {
+ "id": "Uv1Gs8hixSdQGVRH6KLVi",
+ "type": "arrow"
+ },
+ {
+ "id": "qUiA0J-sfT84YpVQWlEot",
+ "type": "arrow"
+ },
+ {
+ "id": "UqXi4k7D7LBkElLUl9bRY",
+ "type": "arrow"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ },
+ {
+ "id": "HHI5Vc7fqPn6VuRzXoIaY",
+ "type": "arrow"
+ },
+ {
+ "id": "pFUtKITgaRpYy9le7pyUv",
+ "type": "arrow"
+ },
+ {
+ "id": "soJdQZVXQySCwfBjVwVtd",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null
+ },
+ {
+ "type": "text",
+ "version": 334,
+ "versionNonce": 1697557396,
+ "isDeleted": false,
+ "id": "Tk2sS1hhVtDPd_0ZZPV5A",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 883.6961749701285,
+ "y": 418.9233360652086,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 118,
+ "height": 26,
+ "seed": 1397655621,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Channel",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Channel"
+ },
+ {
+ "type": "arrow",
+ "version": 2174,
+ "versionNonce": 1259505580,
+ "isDeleted": false,
+ "id": "soJdQZVXQySCwfBjVwVtd",
+ "fillStyle": "hachure",
+ "strokeWidth": 4,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 824.4912274560614,
+ "y": 456.28099618447334,
+ "strokeColor": "#495057",
+ "backgroundColor": "transparent",
+ "width": 239.85269160961423,
+ "height": 0.4292804414828879,
+ "seed": 888195723,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "startBinding": {
+ "elementId": "LZ-zpiSZrRKjK21SSB5me",
+ "gap": 5.461719152599573,
+ "focus": -0.7893003201774047
+ },
+ "endBinding": {
+ "elementId": "bV7tck2JEoFVq7rV8HSWu",
+ "gap": 6.630033682230661,
+ "focus": 0.8270921041462953
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "triangle",
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 239.85269160961423,
+ -0.4292804414828879
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1457,
+ "versionNonce": 184293652,
+ "isDeleted": false,
+ "id": "fYIVRw6i_u72mXigtRsA1",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 677.7013096558908,
+ "y": 501.7765630941593,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 113,
+ "height": 25,
+ "seed": 112951915,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 19.860178682944078,
+ "fontFamily": 1,
+ "text": "provide VSC",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "provide VSC"
+ },
+ {
+ "type": "text",
+ "version": 1228,
+ "versionNonce": 1161605676,
+ "isDeleted": false,
+ "id": "SX9_IjHVCeJsw5zjihNjk",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 677.7013096558908,
+ "y": 684.3587865243416,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 129,
+ "height": 52,
+ "seed": 786765227,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "register \nVSC maturity",
+ "baseline": 44,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "register \nVSC maturity"
+ },
+ {
+ "type": "text",
+ "version": 1489,
+ "versionNonce": 1069536916,
+ "isDeleted": false,
+ "id": "NQpfW4a7Hwtvr-8EPhJwk",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1085.2505196648644,
+ "y": 532.2210075386038,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 93,
+ "height": 26,
+ "seed": 38614437,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 19.860178682944078,
+ "fontFamily": 1,
+ "text": "apply VSC",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "apply VSC"
+ },
+ {
+ "type": "text",
+ "version": 974,
+ "versionNonce": 2109574316,
+ "isDeleted": false,
+ "id": "hlLx6LN5_BUDJUO2t7mzz",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 686.3628416367951,
+ "y": 383.0344471763196,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 1737169669,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 1323,
+ "versionNonce": 1882186772,
+ "isDeleted": false,
+ "id": "yqi6dzvpzblq17ysowkTo",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 639.9183971923508,
+ "y": 334.03444717631965,
+ "strokeColor": "#364fc7",
+ "backgroundColor": "transparent",
+ "width": 138,
+ "height": 26,
+ "seed": 578772677,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Provider Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Provider Chain"
+ },
+ {
+ "type": "text",
+ "version": 1059,
+ "versionNonce": 1446975276,
+ "isDeleted": false,
+ "id": "tLJ6siXC9VChVdOcHR37l",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1101.2517305256843,
+ "y": 391.1455582874308,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 112,
+ "height": 26,
+ "seed": 2057296357,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "CCV Module",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "CCV Module"
+ },
+ {
+ "type": "text",
+ "version": 1658,
+ "versionNonce": 37896596,
+ "isDeleted": false,
+ "id": "Y19fC1uuzabCFyV0s97Ip",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1045.4739527479067,
+ "y": 338.14555828743073,
+ "strokeColor": "#e67700",
+ "backgroundColor": "transparent",
+ "width": 148,
+ "height": 26,
+ "seed": 1161289003,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Consumer Chain",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Consumer Chain"
+ },
+ {
+ "type": "text",
+ "version": 1482,
+ "versionNonce": 1798001068,
+ "isDeleted": false,
+ "id": "_72T1dsBHZpELeZ7ogywj",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1085.2505196648644,
+ "y": 672.2274627737531,
+ "strokeColor": "#000000",
+ "backgroundColor": "#e67700",
+ "width": 129,
+ "height": 50,
+ "seed": 1106855979,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "id": "Mi1I3PpLY1MQGIPxt7fC1",
+ "type": "arrow"
+ },
+ {
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "notify \nVSC maturity",
+ "baseline": 43,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "notify \nVSC maturity"
+ },
+ {
+ "type": "text",
+ "version": 935,
+ "versionNonce": 757919508,
+ "isDeleted": false,
+ "id": "USOjGct8Q4FclCqyREAjw",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1091.4180655283808,
+ "y": 592.8332539375602,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 86,
+ "height": 52,
+ "seed": 1928733029,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "unbonding\nperiod",
+ "baseline": 44,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "unbonding\nperiod"
+ },
+ {
+ "type": "arrow",
+ "version": 1472,
+ "versionNonce": 1001143340,
+ "isDeleted": false,
+ "id": "dYJRFcc1PLcSHVaOF3szx",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "dashed",
+ "roughness": 0,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1083.9873273042986,
+ "y": 674.787472319473,
+ "strokeColor": "#000000",
+ "backgroundColor": "#ced4da",
+ "width": 0.2423078102508498,
+ "height": 109.44664752881295,
+ "seed": 47081765,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "startBinding": {
+ "elementId": "_72T1dsBHZpELeZ7ogywj",
+ "focus": -1.0179406248132712,
+ "gap": 1.2631923605658812
+ },
+ "endBinding": {
+ "elementId": "NQpfW4a7Hwtvr-8EPhJwk",
+ "focus": 1.0326950964223782,
+ "gap": 7.119817252056237
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": "bar",
+ "endArrowhead": "bar",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -0.2423078102508498,
+ -109.44664752881295
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 727,
+ "versionNonce": 1365634196,
+ "isDeleted": false,
+ "id": "RzN1Ejg0DNhbMX38KakLy",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 875.5850638590175,
+ "y": 490.923336065209,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 104,
+ "height": 26,
+ "seed": 253438597,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "VSCPacket",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "VSCPacket"
+ },
+ {
+ "type": "text",
+ "version": 793,
+ "versionNonce": 1370216108,
+ "isDeleted": false,
+ "id": "sQR88kHWeEXVsXgq8udT2",
+ "fillStyle": "hachure",
+ "strokeWidth": 1,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 846.6406194145729,
+ "y": 656.5900027318755,
+ "strokeColor": "#000000",
+ "backgroundColor": "transparent",
+ "width": 197,
+ "height": 25,
+ "seed": 894891877,
+ "groupIds": [],
+ "strokeSharpness": "sharp",
+ "boundElements": [
+ {
+ "type": "arrow",
+ "id": "UdbNDUj-KimwGAw0JZmAm"
+ },
+ {
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1645619130252,
+ "link": null,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": " VSCMaturedPacket",
+ "baseline": 18,
+ "textAlign": "center",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": " VSCMaturedPacket"
+ },
+ {
+ "type": "arrow",
+ "version": 1903,
+ "versionNonce": 1352874516,
+ "isDeleted": false,
+ "id": "pFUtKITgaRpYy9le7pyUv",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 824.0249181995255,
+ "y": 516.6028911169248,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 242.12081022232314,
+ "height": 30.99520266125478,
+ "seed": 1819697989,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130252,
+ "link": null,
+ "startBinding": {
+ "elementId": "LZ-zpiSZrRKjK21SSB5me",
+ "gap": 4.995409896063598,
+ "focus": -0.47343333675257726
+ },
+ "endBinding": {
+ "elementId": "bV7tck2JEoFVq7rV8HSWu",
+ "gap": 4.8282243260578035,
+ "focus": 0.21245077762725106
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 242.12081022232314,
+ 30.99520266125478
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 2365,
+ "versionNonce": 1775509804,
+ "isDeleted": false,
+ "id": "0s6Gf0lhs4RxTdpZNUqsV",
+ "fillStyle": "hachure",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1063.5472546745837,
+ "y": 684.0659396918977,
+ "strokeColor": "#495057",
+ "backgroundColor": "#ced4da",
+ "width": 237.96419827484328,
+ "height": 23.35329422599807,
+ "seed": 105495787,
+ "groupIds": [],
+ "strokeSharpness": "round",
+ "boundElements": [],
+ "updated": 1645619130253,
+ "link": null,
+ "startBinding": {
+ "elementId": "bV7tck2JEoFVq7rV8HSWu",
+ "gap": 7.426698073322591,
+ "focus": -0.4237049945547797
+ },
+ "endBinding": {
+ "elementId": "LZ-zpiSZrRKjK21SSB5me",
+ "gap": 6.553548096278409,
+ "focus": 0.6895846829562841
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "triangle",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -237.96419827484328,
+ 23.35329422599807
+ ]
+ ]
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.png b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.png
new file mode 100644
index 00000000..55eb3247
Binary files /dev/null and b/ibc/next/spec/app/ics-028-cross-chain-validation/figures/ccv-vsc-overview.png differ
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/methods.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/methods.mdx
new file mode 100644
index 00000000..1d208959
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/methods.mdx
@@ -0,0 +1,2326 @@
+---
+title: "CCV: Technical Specification - Methods"
+---
+
+## Outline
+
+- [General Methods](#general-methods)
+ - [BeginBlock and EndBlock](#beginblock-and-endblock)
+ - [Packet Relay](#packet-relay)
+- [Sub-protocols](#sub-protocols)
+ - [Initialization](#initialization)
+ - [Consumer Chain Removal](#consumer-chain-removal)
+ - [Validator Set Update](#validator-set-update)
+ - [Consumer Initiated Slashing](#consumer-initiated-slashing)
+ - [Reward Distribution](#reward-distribution)
+
+## General Methods
+
+[↑ Back to Outline](#outline)
+
+To express the error conditions, the following specification of the sub-protocols uses the exception system of the host state machine, which is exposed through two functions (as defined in [ICS 24](../../core/ics-024-host-requirements)): `abortTransactionUnless` and `abortSystemUnless`.
+
+### BeginBlock and EndBlock
+
+[↑ Back to Outline](#outline)
+
+The functions `BeginBlock()` and `EndBlock()` (see [Implemented Interfaces](./technical_specification#implemented-interfaces)) are split across the CCV sub-protocols.
+
+#### **[CCV-PCF-BBLOCK.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the AppModule interface
+function BeginBlock() {
+ BeginBlockInit()
+ BeginBlockCCR()
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `BeginBlockInit()` is invoked (see [[CCV-PCF-BBLOCK-INIT.1]](#ccv-pcf-bblock-init1), i.e., it contains the `BeginBlock()` logic needed for the Initialization sub-protocol).
+ - `BeginBlockCCR()` is invoked (see [[CCV-PCF-BBLOCK-CCR.1]](#ccv-pcf-bblock-ccr1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Chain Removal sub-protocol).
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-EBLOCK.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the AppModule interface
+function EndBlock(): [ValidatorUpdate] {
+ EndBlockCIS()
+ EndBlockCCR()
+ EndBlockVSU()
+
+ // do not return anything to the consensus engine
+ return []
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `EndBlockCIS()` is invoked (see [[CCV-PCF-EBLOCK-CIS.1]](#ccv-pcf-eblock-cis1), i.e., it contains the `EndBlock()` logic needed for the Consumer Initiated Slashing sub-protocol).
+ - `EndBlockCCR()` is invoked (see [[CCV-PCF-EBLOCK-CCR.1]](#ccv-pcf-eblock-ccr1), i.e., it contains the `EndBlock()` logic needed for the Consumer Chain Removal sub-protocol).
+ - `EndBlockVSU()` is invoked (see [[CCV-PCF-EBLOCK-VSU.1]](#ccv-pcf-eblock-vsu1), i.e., it contains the `EndBlock()` logic needed for the Validator Set Update sub-protocol).
+- **Error Condition**
+ - None.
+
+> **Note**: The provider CCV module expects the provider Staking module to update its view of the validator set before the `EndBlock()` of the provider CCV module is invoked.
+> A solution is for the provider Staking module to update its view during `EndBlock()` and then, the `EndBlock()` of the provider Staking module to be executed before the `EndBlock()` of the provider CCV module.
+
+---
+
+{/* omit in toc */}
+#### **[CCV-CCF-BBLOCK.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the AppModule interface
+function BeginBlock() {
+ BeginBlockInit()
+ BeginBlockCCR()
+ BeginBlockCIS()
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `BeginBlockInit()` is invoked (see [[CCV-CCF-BBLOCK-INIT.1]](#ccv-ccf-bblock-init1), i.e., it contains the `BeginBlock()` logic needed for the Channel Initialization sub-protocol).
+ - `BeginBlockCCR()` is invoked (see [[CCV-CCF-BBLOCK-CCR.1]](#ccv-ccf-bblock-ccr1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Chain Removal sub-protocol).
+ - `BeginBlockCIS()` is invoked (see [[CCV-CCF-BBLOCK-CIS.1]](#ccv-ccf-bblock-cis1), i.e., it contains the `BeginBlock()` logic needed for the Consumer Initiated Slashing sub-protocol).
+- **Error Condition**
+ - None.
+
+#### **[CCV-CCF-EBLOCK.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the AppModule interface
+function EndBlock(): [ValidatorUpdate] {
+ EndBlockRD()
+
+ // return the validator set updates to the consensus engine
+ return EndBlockVSU()
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True. x
+- **Postcondition**
+ - `EndBlockRD()` is invoked (see [[CCV-PCF-EBLOCK-RD.1]](#ccv-ccf-eblock-rd1), i.e., it contains the `EndBlock()` logic needed for the Reward Distribution sub-protocol).
+ - `EndBlockVSU()` is invoked and the return value is returned to the consensus engine (see [[CCV-CCF-EBLOCK-VSU.1]](#ccv-ccf-eblock-vsu1), i.e., it contains the `EndBlock()` logic needed for the Validator Set Update sub-protocol).
+- **Error Condition**
+ - None.
+
+### Packet Relay
+
+[↑ Back to Outline](#outline)
+
+{/* omit in toc */}
+#### **[CCV-PCF-RCVP.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onRecvPacket(packet: Packet): bytes {
+ switch typeof(packet.data) {
+ case VSCMaturedPacketData:
+ return onRecvVSCMaturedPacket(packet)
+ case SlashPacketData:
+ return onRecvSlashPacket(packet)
+ default:
+ // unexpected packet type
+ return PacketError
+ }
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a packet on a channel owned by the provider CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the packet is a `VSCMaturedPacket`, the acknowledgement obtained from invoking the `onRecvVSCMaturedPacket` method is returned.
+ - If the packet is a `SlashPacket`, the acknowledgement obtained from invoking the `onRecvSlashPacket` method is returned.
+ - Otherwise, an error acknowledgement is returned.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-ACKP.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onAcknowledgePacket(packet: Packet, ack: bytes) {
+ switch typeof(packet.data) {
+ case VSCPacketData:
+ onAcknowledgeVSCPacket(packet, ack)
+ default:
+ // unexpected packet type
+ abortTransactionUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives an acknowledgement on a channel owned by the provider CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the acknowledgement is for a `VSCPacket`, the `onAcknowledgeVSCPacket` method is invoked.
+ - Otherwise, the transaction is aborted.
+- **Error Condition**
+ - None.
+
+#### **[CCV-PCF-TOP.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onTimeoutPacket(packet Packet) {
+ switch typeof(packet.data) {
+ case VSCPacketData:
+ onTimeoutVSCPacket(packet)
+ default:
+ // unexpected packet type
+ abortTransactionUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - A packet sent on a channel owned by the provider CCV module timed out as a result of either
+ - the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end));
+ - or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)).
+- **Precondition**
+ - The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section).
+- **Postcondition**
+ - If the timeout is for a `VSCPacket`, the `onTimeoutVSCPacket` method is invoked.
+ - Otherwise, the transaction is aborted.
+- **Error Condition**
+ - None.
+
+---
+
+#### **[CCV-CCF-RCVP.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onRecvPacket(packet: Packet): bytes {
+ switch typeof(packet.data) {
+ case VSCPacketData:
+ return onRecvVSCPacket(packet)
+ default:
+ // unexpected packet type
+ return PacketError
+ }
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a packet on a channel owned by the consumer CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the packet is a `VSCPacket`, the acknowledgement obtained from invoking the `onRecvVSCPacket` method is returned.
+ - Otherwise, an error acknowledgement is returned.
+- **Error Condition**
+ - None.
+
+#### **[CCV-CCF-ACKP.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onAcknowledgePacket(packet: Packet, ack: bytes) {
+ switch typeof(packet.data) {
+ case VSCMaturedPacketData:
+ onAcknowledgeVSCMaturedPacket(packet, ack)
+ case SlashPacketData:
+ onAcknowledgeSlashPacket(packet, ack)
+ default:
+ // unexpected packet type
+ abortTransactionUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives an acknowledgement on a channel owned by the consumer CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the acknowledgement is for a `VSCMaturedPacket`, the `onAcknowledgeVSCMaturedPacket` method is invoked.
+ - If the acknowledgement is for a `SlashPacket`, the `onAcknowledgeSlashPacket` method is invoked.
+ - Otherwise, the transaction is aborted.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-TOP.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onTimeoutPacket(packet Packet) {
+ switch typeof(packet.data) {
+ case VSCMaturedPacketData:
+ onTimeoutVSCMaturedPacket(packet)
+ case SlashPacketData:
+ onTimeoutSlashPacket(packet)
+ default:
+ // unexpected packet type
+ abortTransactionUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - A packet sent on a channel owned by the consumer CCV module timed out as a result of either
+ - the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end));
+ - or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)).
+- **Precondition**
+ - The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section).
+- **Postcondition**
+ - If the timeout is for a `VSCMaturedPacket`, the `onTimeoutVSCMaturedPacket` method is invoked.
+ - If the timeout is for a `SlashPacket`, the `onTimeoutSlashPacket` method is invoked.
+ - Otherwise, the transaction is aborted.
+- **Error Condition**
+ - None.
+
+## Sub-protocols
+
+### Initialization
+
+[↑ Back to Outline](#outline)
+
+The *initialization* sub-protocol enables a provider chain and a consumer chain to create a CCV channel -- a unique, ordered IBC channel for exchanging packets. As a prerequisite, the initialization sub-protocol MUST create two IBC clients, one on the provider chain to the consumer chain and one on the consumer chain to the provider chain. This is necessary to verify the identity of the two chains (as long as the clients are trusted).
+
+#### **[CCV-PCF-INITG.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the AppModule interface
+function InitGenesis(state: ProviderGenesisState): [ValidatorUpdate] {
+ // bind to ProviderPortId port
+ err = portKeeper.bindPort(ProviderPortId)
+ // check whether the capability for the port can be claimed
+ abortSystemUnless(err == nil)
+
+ foreach cs in state.consumerStates {
+ abortSystemUnless(validateChannelIdentifier(cs.channelId))
+ chainToChannel[cs.chainId] = cs.channelId
+ channelToChain[cs.channelId] = cc.chainId
+ }
+
+ // do not return anything to the consensus engine
+ return []
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - An `InitChain` message is received from the consensus engine; the `InitChain` message is sent when the provider chain is first started.
+- **Precondition**
+ - The provider CCV module is in the initial state.
+- **Postcondition**
+ - The capability for the port `ProviderPortId` is claimed.
+ - For each consumer state in the `ProviderGenesisState`, the initial state is set, i.e., the following mappings `chainToChannel`, `channelToChain` are set.
+- **Error Condition**
+ - The capability for the port `ProviderPortId` cannot be claimed.
+ - For any consumer state in the `ProviderGenesisState`, the channel ID is not valid (cf. the validation function defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+
+#### **[CCV-PCF-HCAPROP.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements governance proposal Handler
+function HandleConsumerAdditionProposal(p: ConsumerAdditionProposal) {
+ // store the proposal as a pending addition proposal
+ pendingConsumerAdditionProposals.Append(p)
+}
+```
+
+- **Caller**
+ - `EndBlock()` method of Governance module.
+- **Trigger Event**
+ - A governance proposal `ConsumerAdditionProposal` has passed (i.e., it got the necessary votes).
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The proposal is appended to the list of pending addition proposals, i.e., `pendingConsumerAdditionProposals`.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-BBLOCK-INIT.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function BeginBlockInit() {
+ // iterate over the pending addition proposals and create
+ // the consumer client if the spawn time has passed
+ foreach p IN pendingConsumerAdditionProposals {
+ if currentTimestamp() > p.spawnTime {
+ CreateConsumerClient(p)
+ pendingConsumerAdditionProposals.Remove(p)
+ }
+ }
+}
+```
+
+- **Caller**
+ - The `BeginBlock()` method.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - For each `ConsumerAdditionProposal` `p` in the list of pending addition proposals `pendingConsumerAdditionProposals`, if `currentTimestamp() > p.spawnTime`, then
+ - `CreateConsumerClient(p)` is invoked;
+ - `p` is removed from `pendingConsumerAdditionProposals`.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-CRCLIENT.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// Utility method
+function CreateConsumerClient(p: ConsumerAdditionProposal) {
+ // check that no other consumer chain with the same chain ID exists
+ if p.chainId IN chainToClient.Keys() {
+ // ignore governance proposal
+ return
+ }
+
+ // set consumer chain initial validator set, i.e.,
+ // the validator set is the same as the validator set
+ // from own consensus state at current height
+ //
+ // TODO: ownConsensusState.validatorSet VS consensusState.nextValidatorsHash
+ // specify which validator set is used as the initial val set
+ ownConsensusState = getConsensusState(getCurrentHeight())
+ initialValSet = ownConsensusState.validatorSet
+
+ if p.connId != "" { // connection ID provided
+ // check validity
+ connectionEnd = provableStore.get("connections/{p.connId}")
+ if connectionEnd == nil {
+ // invalid proposal: cannot find connection
+ return
+ }
+ clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")
+ if clientState.chainID != p.chainId {
+ // invalid proposal: connection not to expected chain ID
+ return
+ }
+
+ // store client ID
+ chainToClient[p.chainId] = connectionEnd.clientIdentifier
+ // store connection ID
+ chainToConnection[p.chainId] = connId
+
+ // create and store ConsumerGenesisState
+ consumerGenesisState[p.chainId] = ConsumerGenesisState {
+ // consumer chain MUST start in pre-CCV state, i.e.,
+ // the consumer CCV module MUST NOT pass validator updates
+ // to the underlying consensus engine
+ preCCV: true,
+ unbondingPeriod: p.unbondingPeriod,
+ connId: connectionEnd.counterpartyConnectionIdentifier,
+ providerClientState: nil,
+ providerConsensusState: nil,
+ counterpartyClientId: "",
+ initialValSet: initialValSet,
+ transferChannelId: p.transferChannelId,
+ }
+ }
+ else {
+ // create client state
+ clientState = ClientState{
+ chainId: p.chainId,
+ unbondingPeriod: p.unbondingPeriod,
+ // the height when the client was last updated is set to the first possible height;
+ // for example, in the case of a Tendermint Client, this is Height{0, 1} (see ICS-7)
+ latestHeight: 0,
+ }
+ // create consensus state
+ consensusState = ConsensusState{
+ validatorSet: initialValSet,
+ }
+ // create consumer chain client and store it
+ clientId = clientKeeper.CreateClient(clientState, consensusState)
+ chainToClient[p.chainId] = clientId
+
+ // create and store ConsumerGenesisState
+ consumerGenesisState[p.chainId] = ConsumerGenesisState {
+ // consumer chain MUST NOT start in pre-CCV state, i.e.,
+ // the consumer CCV module MUST pass validator updates
+ // to the underlying consensus engine
+ preCCV: false,
+ unbondingPeriod: p.unbondingPeriod,
+ connId: "",
+ providerClientState: getHostClientState(getCurrentHeight()),
+ providerConsensusState: ownConsensusState,
+ counterpartyClientId: clientId,
+ initialValSet: initialValSet,
+ transferChannelId: p.transferChannelId,
+ }
+ }
+
+ // store lockUnbondingOnTimeout flag
+ lockUnbondingOnTimeout[p.chainId] = p.lockUnbondingOnTimeout
+
+ // add init timeout timestamp for this consumer chain
+ initTimeoutTimestamps[p.chainId] = currentTimestamp().Add(initTimeout)
+}
+```
+
+- **Caller**
+ - Either `HandleConsumerAdditionProposal` (see [CCV-PCF-HCAPROP.1](#ccv-pcf-hcaprop1)) or `BeginBlockInit()` (see [CCV-PCF-BBLOCK-INIT.1](#ccv-pcf-bblock-init1)).
+- **Trigger Event**
+ - A governance proposal `ConsumerAdditionProposal` `p` has passed (i.e., it got the necessary votes).
+- **Precondition**
+ - `currentTimestamp() > p.spawnTime`.
+- **Postcondition**
+ - If a client for `p.chainId` already exists, the state is not changed.
+ - Otherwise,
+ - the validator set of the provider chain own consensus state at current height is set as the initial validator set of the consumer chain;
+ - if `p.connId` is set, then
+ - if a connection end with ID `p.connId` cannot be found, the state is not changed;
+ - otherwise,
+ - if the connection with ID `p.connId` is not to the chain with ID `p.chainId`, the state is not changed;
+ - otherwise,
+ - both the client ID and connection ID are stored;
+ - a `ConsumerGenesisState` is created and stored;
+ - otherwise,
+ - otherwise,
+ - a client state is created with `chainId = p.chainId` and `unbondingPeriod = p.unbondingPeriod`;
+ - a consensus state is created with `validatorSet` set to the initial validator set of the consumer chain;
+ - a client of the consumer chain is created and the client ID is stored;
+ - a `ConsumerGenesisState` is created and stored;
+ - `lockUnbondingOnTimeout[p.chainId]` is set to `p.lockUnbondingOnTimeout`.
+ - The init timeout timestamp is computed and stored in `initTimeoutTimestamps[p.chainId]`.
+- **Error Condition**
+ - None.
+
+> **Note:** For the case when the `clientId` field of the `ConsumerAdditionProposal` is not set, creating a client of a remote chain requires a `ClientState` and a `ConsensusState` (for an example, take a look at [ICS 7](../../client/ics-007-tendermint-client)).
+> `ConsensusState` requires setting a validator set of the remote chain.
+> The provider chain uses the fact that the validator set of the consumer chain is the same as its own validator set.
+>
+> **Note:** Bootstrapping the consumer CCV module requires a `ConsumerGenesisState` (see the [CCV Data Structures](./data_structures#ccv-data-structures) section). The provider CCV module creates such a `ConsumerGenesisState` when handling a governance proposal `ConsumerAdditionProposal`.
+>
+> **Note:** If the channel initialization for a consumer chain exceeds the `initTimeout` period, then the provider chain removes that consumer.
+> As a result, all further attempts on the consumer side to established the CCV channel will fail.
+> This means that the consumer chain requires some sort of social consensus to either restart the process of becoming a consumer chain or transitioning back to a sovereign chain.
+
+{/* omit in toc */}
+#### **[CCV-PCF-COINIT.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): string {
+ // the channel handshake MUST be initiated by consumer chain
+ abortTransactionUnless(FALSE)
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanOpenInit` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is always aborted; hence, the state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-COTRY.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): string {
+ // validate parameters:
+ // - only ordered channels allowed
+ abortTransactionUnless(order == ORDERED)
+ // - require the portIdentifier to be the port ID the CCV module is bound to
+ abortTransactionUnless(portIdentifier == ProviderPortId)
+
+ // assert that the counterpartyPortIdentifier matches
+ // the expected consumer port ID
+ abortTransactionUnless(counterpartyPortIdentifier == ConsumerPortId)
+
+ // assert that the counterpartyVersion matches the expected version
+ abortTransactionUnless(counterpartyVersion == ccvVersion)
+
+ // get the client state associated with the underlying client
+ channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
+ abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
+ connId = channelEnd.connectionHops[0]
+ connectionEnd = provableStore.get("connections/{connId}")
+ clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")
+
+ if clientState.chainId IN chainToConnection.Keys() {
+ // if a connection is stored for this consumer chain,
+ // verify that the underlying connection is the expected one
+ abortTransactionUnless(chainToConnection[clientState.chainId] == connId)
+ }
+
+ // verify that the underlying client is the expected client of the consumer chain
+ abortTransactionUnless(chainToClient[clientState.chainId] == connectionEnd.clientIdentifier)
+
+ // require that no other CCV channel exists for this consumer chain
+ abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())
+
+ return CCVHandshakeMetadata{
+ providerDistributionAccount: GetDistributionAccountAddress(),
+ version: ccvVersion
+ }
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanOpenTry` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is aborted if any of the following conditions are true:
+ - the channel is not ordered;
+ - `portIdentifier != ProviderPortId`;
+ - `counterpartyPortIdentifier != ConsumerPortId`;
+ - `counterpartyVersion != ccvVersion`;
+ - no channel with `portIdentifier` and `channelIdentifier` exists;
+ - the channel has more than one connection hop;
+ - a connection is stored for this consumer chain and doesn't match the underlying connection of this channel;
+ - the channel is not built on top of the client created for this consumer chain;
+ - another CCV channel for this consumer chain already exists.
+ - A `CCVHandshakeMetadata` is returned, with `providerDistributionAccount` set to the address of the distribution module account on the provider chain and `version` set to `ccvVersion`.
+ - The state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-COACK.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ // the channel handshake MUST be initiated by consumer chain
+ abortTransactionUnless(FALSE)
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanOpenAck` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is always aborted; hence, the state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-COCONFIRM.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // get the client state associated with the underlying client
+ channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
+ abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
+ connId = channelEnd.connectionHops[0]
+ connectionEnd = provableStore.get("connections/{connId}")
+ clientState = provableStore.get("clients/{connectionEnd.clientIdentifier}/clientState")
+
+ // require that no other CCV channel exists for this consumer chain;
+ // note: this is a sanity check; this check should always pass by construction
+ abortTransactionUnless(clientState.chainId NOTIN chainToChannel.Keys())
+
+ // set channel mappings
+ chainToConnection[clientState.chainId] = connId
+ chainToChannel[clientState.chainId] = channelIdentifier
+ channelToChain[channelIdentifier] = clientState.chainId
+ // set initialHeights for this consumer chain
+ initialHeights[chainId] = getCurrentHeight()
+
+ // remove init timeout timestamp
+ initTimeoutTimestamps.Remove(clientState.chainId)
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanOpenConfirm` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is aborted if any of the following conditions are true:
+ - no channel with `portIdentifier` and `channelIdentifier` exists;
+ - the channel has more than one connection hop;
+ - another CCV channel for this consumer chain already exists.
+ - The connection mapping is set, i.e., `chainToConnection`.
+ - The channel mappings are set, i.e., `chainToChannel` and `channelToChain`.
+ - `initialHeights[chainId]` is set to the current height.
+ - The init timeout timestamp for the consumer chain with ID `clientState.chainId` is removed.
+- **Error Condition**
+ - None.
+
+---
+
+{/* omit in toc */}
+#### **[CCV-CCF-INITG.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the AppModule interface
+function InitGenesis(gs: ConsumerGenesisState): [ValidatorUpdate] {
+ // ValidateGenesis
+ // - contains a non-empty initial validator set
+ abortSystemUnless(gs.initialValSet NOT empty)
+ if gs.preCCV {
+ // - contains a valid connId
+ connectionEnd = provableStore.get("connections/{gs.connId}")
+ abortSystemUnless(connectionEnd != nil)
+ }
+ else {
+ // - contains a valid providerClientState
+ abortSystemUnless(gs.providerClientState != nil AND gs.providerClientState.Valid())
+ // - contains a valid providerConsensusState
+ abortSystemUnless(gs.providerConsensusState != nil AND gs.providerConsensusState.Valid())
+ // - contains an initial validator set that matches
+ // the validator set in the providerConsensusState (e.g., ICS 7)
+ abortSystemUnless(gs.initialValSet == gs.providerConsensusState.validatorSet)
+ }
+ if gs.transferChannelId != "" {
+ // - if transferChannelId is provided, it must the ID
+ // of a channel connected to the "transfer" port
+ channelEnd = provableStore.get("channelEnds/ports/transfer/channels/{gs.transferChannelId}")
+ abortSystemUnless(channelEnd != nil)
+ }
+
+ // bind to ConsumerPortId port
+ err = portKeeper.bindPort(ConsumerPortId)
+ // check whether the capability for the port can be claimed
+ abortSystemUnless(err == nil)
+
+ // set pre-CCV state
+ preCCV = gs.preCCV
+
+ if preCCV {
+ // start consumer chain in pre-CCV state;
+ // store the ID of the client of the provider chain
+ providerClientId = connectionEnd.clientIdentifier
+ }
+ else {
+ // start consumer chain in normal CCV state;
+ // create client of the provider chain and store the ID
+ providerClientId = clientKeeper.CreateClient(gs.providerClientState, gs.providerConsensusState)
+ }
+
+ // set the consumer unbonding period
+ ConsumerUnbondingPeriod = gs.unbondingTime
+
+ // set default value for HtoVSC
+ HtoVSC[getCurrentHeight()] = 0
+
+ // set the initial validator set for the consumer chain
+ foreach val IN gs.initialValSet {
+ ccvValidatorSet[hash(val.pubKey)] = val
+ }
+
+ // set distribution channel ID
+ distributionChannelId = gs.transferChannelId
+
+ // initiate handshake
+ if preCCV {
+ // initiate CCV channel opening handshake
+ // i.e., use handleChanOpenInit as defined in ICS-26
+ datagram = ChanOpenInit{
+ order: ORDERED,
+ connectionHops: [gs.connId],
+ portIdentifier: ConsumerPortId,
+ counterpartyPortIdentifier: ProviderPortId,
+ version: ccvVersion,
+ }
+ handleChanOpenInit(datagram)
+ }
+ else {
+ // initiate connection opening handshake
+ // i.e., use handleConnOpenInit as defined in ICS-26
+ datagram = ConnOpenInit{
+ clientIdentifier: providerClientId,
+ counterpartyClientIdentifier: gs.counterpartyClientId,
+ version: "ccv"
+ }
+ connId = handleConnOpenInit(datagram)
+
+ // initiate CCV channel opening handshake
+ // i.e., use handleChanOpenInit as defined in ICS-26
+ datagram = ChanOpenInit{
+ order: ORDERED,
+ connectionHops: [connId],
+ portIdentifier: ConsumerPortId,
+ counterpartyPortIdentifier: ProviderPortId,
+ version: ccvVersion,
+ }
+ handleChanOpenInit(datagram)
+ }
+
+ return gs.initialValSet
+}
+```
+
+- **Caller**
+ - The ABCI application.
+- **Trigger Event**
+ - An `InitChain` message is received from the consensus engine; the `InitChain` message is sent when the consumer chain is first started.
+- **Precondition**
+ - The consumer CCV module is in the initial state.
+- **Postcondition**
+ - The capability for the port `ConsumerPortId` is claimed.
+ - `preCCV` is set to `gs.preCCV`.
+ - If `preCCV == true`, the ID of the client on which the connection with `gs.connId` is built is stored into `providerClientId`.
+ - Otherwise, a client of the provider chain is created and the client ID is stored into `providerClientId`.
+ - `ConsumerUnbondingPeriod` is set to `gs.unbondingPeriod`.
+ - `HtoVSC` for the current block is set to `0`.
+ - The `ccvValidatorSet` mapping is populated with the initial validator set.
+ - The ID of the distribution token transfer channel is set to `gs.transferChannelId`.
+ - If `preCCV == true`, the CCV channel opening handshake is initialized.
+ - Otherwise, the connection opening handshake is initialized.
+ - The initial validator set is returned to the consensus engine.
+- **Error Condition**
+ - The genesis state contains an empty initial validator set.
+ - If the genesis state `preCCV` field is set to `true`, then the genesis state contains no valid connection ID.
+ - Otherwise,
+ - the genesis state contains no valid provider client state, where the validity is defined in the corresponding client specification (e.g., [ICS 7](../../client/ics-007-tendermint-client);
+ - the genesis state contains no valid provider consensus state, where the validity is defined in the corresponding client specification (e.g., [ICS 7](../../client/ics-007-tendermint-client));
+ - the genesis state contains an initial validator set that does not match the validator set in the provider consensus state;
+ - The genesis state contains an invalid distribution channel ID.
+ - The capability for the port `ConsumerPortId` cannot be claimed.
+
+> **Note**: CCV assumes that all the correct validators in the initial validator set of the consumer chain receive the *same* consumer chain binary and consumer chain genesis state.
+> Although the mechanism of disseminating the binary and the genesis state is outside the scope of this specification, a possible approach would entail including this information in the governance proposal on the provider chain.
+
+{/* omit in toc */}
+#### **[CCV-CCF-COINIT.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): string {
+ // ensure provider channel hasn't already been created
+ abortTransactionUnless(providerChannel == "")
+
+ // validate parameters:
+ // - only ordered channels allowed
+ abortTransactionUnless(order == ORDERED)
+ // - require the portIdentifier to be the port ID the CCV module is bound to
+ abortTransactionUnless(portIdentifier == ConsumerPortId)
+ // - require the version to be the expected version
+ abortTransactionUnless(version == "" OR version == ccvVersion)
+
+ // assert that the counterpartyPortIdentifier matches
+ // the expected consumer port ID
+ abortTransactionUnless(counterpartyPortIdentifier == ProviderPortId)
+
+ // require that the client ID of the client associated
+ // with this channel matches the expected provider client id
+ channelEnd = provableStore.get("channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}")
+ abortTransactionUnless(channelEnd != nil AND len(channelEnd.connectionHops) == 1)
+ connId = channelEnd.connectionHops[0]
+ connectionEnd = provableStore.get("connections/{connId}")
+ abortTransactionUnless(providerClientId != connectionEnd.clientIdentifier)
+
+ return ccvVersion
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanOpenInit` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is aborted if any of the following conditions are true:
+ - `providerChannel` is already set;
+ - `portIdentifier != ConsumerPortId`;
+ - `version` is set but not to the expected version;
+ - `counterpartyPortIdentifier != ProviderPortId`;
+ - the client associated with this channel is not the expected provider client.
+ - `ccvVersion` is returned.
+ - The state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-COTRY.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): string {
+ // the channel handshake MUST be initiated by consumer chain
+ abortTransactionUnless(FALSE)
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanOpenTry` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is always aborted; hence, the state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-COACK.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ // ensure provider channel hasn't already been created
+ abortTransactionUnless(providerChannel == "")
+
+ // the version must be encoded in JSON format (as defined in ICS4)
+ md = UnmarshalJSON(counterpartyVersion)
+
+ // assert that the counterpartyVersion matches the expected version
+ abortTransactionUnless(md.version == ccvVersion)
+
+ // set the address of the distribution module account on the provider chain
+ providerDistributionAccount = md.providerDistributionAccount
+
+ if distributionChannelId == "" {
+ // initiate opening handshake for the distribution token transfer channel
+ // over the same connection as the CCV channel
+ // i.e., use handleChanOpenInit as defined in ICS-26
+ datagram = ChanOpenInit{
+ order: UNORDERED,
+ connectionHops: channelKeeper.GetConnectionHops(channelIdentifier), // same as the CCV channel
+ portIdentifier: "transfer",
+ counterpartyPortIdentifier: "transfer",
+ version: "ics20-1",
+ }
+ distributionChannelId = handleChanOpenInit(datagram)
+ }
+
+ // set the channel as the provider channel
+ providerChannel = channelIdentifier
+
+ // send pending slash requests;
+ // note: this can happen only if preCCV == false, as the ABCI application
+ // can invoke SendSlashRequest only once the chain is upgraded to
+ // a consumer chain, see BeginBlockInit below
+ SendPendingSlashRequests()
+
+ if preCCV {
+ // replace valset with initial valset
+ stakingKeeper.ReplaceValset(ccvValidatorSet.Values())
+ }
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanOpenAck` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `counterpartyVersion` is unmarshaled into a `CCVHandshakeMetadata` structure `md`.
+ - The transaction is aborted if any of the following conditions are true:
+ - `providerChannel` is already set;
+ - `md.version != ccvVersion`.
+ - The address of the distribution module account on the provider chain is set to `md.providerDistributionAccount`.
+ - If `distributionChannelId` is not set, the distribution token transfer channel opening handshake is initiated and `distributionChannelId` is set to the resulting channel ID.
+ - The CCV channel is marked as established, i.e., `providerChannel` is set to this channel.
+ - The pending slash requests are sent to the provider chain (see [[CCV-CCF-SNDPESLASH.1]](#ccv-ccf-sndpeslash1)).
+ Note that this can happen only if `preCCV == false`, as the ABCI application can invoke `SendSlashRequest` only once the chain is upgraded to a consumer chain (see [[CCV-CCF-BBLOCK-INIT.1]](#ccv-ccf-bblock-init1)).
+ - If `preCCV == true`, the valset in the staking module is replaced with the `ccvValidatorSet`, i.e., the initial validator set.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-COCONFIRM.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // the channel handshake MUST be initiated by consumer chain
+ abortTransactionUnless(FALSE)
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanOpenConfirm` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is always aborted; hence, the state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-BBLOCK-INIT.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function BeginBlockInit() {
+ if preCCV {
+ ownConsensusState = getConsensusState(getCurrentHeight())
+ if ownConsensusState.validatorSet == ccvValidatorSet.Values() {
+ // pre-CCV state is over; upgrade chain to consumer chain
+ // - set preCCV to false
+ // - the existing staking module no longer provides
+ // validator updates to the underlying consensus engine
+ // - the CCV module starts providing validator updates
+ // to the underlying consensus engine
+ // - for safety, the existing staking module must be kept
+ // for at least the unbonding period
+ }
+ }
+}
+```
+
+- **Caller**
+ - The `BeginBlock()` method.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If `preCCV == true` and the current validator set matches the `ccvValidatorSet` (i.e., the initial validator set), then the chain MUST be upgraded to a full consumer chain.
+ The upgrade mechanism is outside the scope of this specification.
+- **Error Condition**
+ - None.
+
+### Consumer Chain Removal
+
+[↑ Back to Outline](#outline)
+
+{/* omit in toc */}
+#### **[CCV-PCF-HCRPROP.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements governance proposal Handler
+function HandleConsumerRemovalProposal(p: ConsumerRemovalProposal) {
+ // store the proposal as a pending removal proposal
+ pendingConsumerRemovalProposals.Append(p)
+}
+```
+
+- **Caller**
+ - `EndBlock()` method of Governance module.
+- **Trigger Event**
+ - A governance proposal `ConsumerRemovalProposal` has passed (i.e., it got the necessary votes).
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The proposal is appended to the list of pending removal proposals, i.e., `pendingConsumerRemovalProposals`.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-BBLOCK-CCR.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function BeginBlockCCR() {
+ // iterate over the pending removal proposals
+ // and stop the consumer chain
+ foreach p IN pendingConsumerRemovalProposals {
+ if currentTimestamp() > p.stopTime {
+ // stop the consumer chain and do not lock the unbonding
+ StopConsumerChain(p.chainId, false)
+ pendingConsumerRemovalProposals.Remove(p)
+ }
+ }
+}
+```
+
+- **Caller**
+ - The `BeginBlock()` method.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - For each `ConsumerRemovalProposal` `p` in the list of pending removal proposals `pendingConsumerRemovalProposals`, if `currentTimestamp() > p.stopTime`, then
+ - `StopConsumerChain(p.chainId, false)` is invoked;
+ - `p` is removed from `pendingConsumerRemovalProposals`.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-STCC.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function StopConsumerChain(chainId: string, lockUnbonding: Bool) {
+ // check that a client for chainId exists
+ if chainId NOT IN chainToClient.Keys() {
+ return
+ }
+
+ // cleanup state
+ chainToClient.Remove(chainId)
+ lockUnbondingOnTimeout.Remove(chainId)
+ if chainId IN chainToChannel.Keys() {
+ // CCV channel is established
+ channelToChain.Remove(chainToChannel[chainId])
+ channelKeeper.ChanCloseInit(chainToChannel[chainId])
+ chainToChannel.Remove(chainId)
+ }
+ pendingVSCPackets.Remove(chainId)
+ initialHeights.Remove(chainId)
+ downtimeSlashRequests.Remove(chainId)
+ initTimeoutTimestamps.Remove(chainId)
+ vscSendTimestamps.Remove((chainId, *))
+
+ if !lockUnbonding {
+ // remove chainId form all outstanding unbonding operations
+ foreach id IN vscToUnbondingOps[(chainId, _)] {
+ unbondingOps[id].unbondingChainIds.Remove(chainId)
+ // if the unbonding operation has unbonded on all consumer chains
+ if unbondingOps[id].unbondingChainIds.IsEmpty() {
+ // append the id of the unbonding to maturedUnbondingOps
+ maturedUnbondingOps.Append(id)
+ // remove unbonding operation
+ unbondingOps.Remove(id)
+ }
+ }
+ // clean up vscToUnbondingOps mapping
+ vscToUnbondingOps.Remove((chainId, _))
+ }
+}
+```
+
+- **Caller**
+ - `HandleConsumerRemovalProposal` (see [CCV-PCF-HCRPROP.1](#ccv-pcf-hcrprop1))
+ or `BeginBlockCCR()` (see [CCV-PCF-BBLOCK-CCR.1](#ccv-pcf-bblock-ccr1))
+ or `onTimeoutVSCPacket()` (see [CCV-PCF-TOVSC.1](#ccv-pcf-tovsc1))
+ or `EndBlockCCR()` (see [CCV-PCF-EBLOCK-CCR.1](#ccv-pcf-eblock-ccr1)).
+- **Trigger Event**
+ - One of the following events:
+ - a governance proposal to stop the consumer chain with `chainId` has passed (i.e., it got the necessary votes);
+ - a `VSCPacket` sent on the CCV channel to the consumer chain with `chainId` has timed out;
+ - the channel initialization has timed out.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If a client for `p.chainId` does not exist, the state is not changed.
+ - Otherwise,
+ - the client ID mapped to `chainId` in `chainToClient` is removed;
+ - the value mapped to `chainId` in `lockUnbondingOnTimeout` is removed;
+ - if the CCV channel to the consumer chain with `chainId` is established, then
+ - the chain ID mapped to `chainToChannel[chainId]` in `channelToChain` is removed;
+ - the channel closing handshake is initiated for the CCV channel;
+ - the channel ID mapped to `chainId` in `chainToChannel` is removed.
+ - all the `VSCPacketData` mapped to `chainId` in `pendingVSCPackets` are removed;
+ - the height mapped to `chainId` in `initialHeights` is removed;
+ - `downtimeSlashRequests[chainId]` is emptied;
+ - if `lockUnbonding == false`, then
+ - `chainId` is removed from all outstanding unbonding operations;
+ - if an outstanding unbonding operation has matured on all consumer chains,
+ - the matured unbonding operation is added to `maturedUnbondingOps`;
+ - the matured unbonding operation is removed from `unbondingOps`;
+ - all the entries with `chainId` are removed from the `vscToUnbondingOps` mapping.
+- **Error Condition**
+ - None
+
+> **Note**: Invoking `StopConsumerChain(chainId, lockUnbonding)` with `lockUnbonding == FALSE` entails that all outstanding unbonding operations can complete before `ConsumerUnbondingPeriod` elapses on the consumer chain with `chainId`.
+> Thus, invoking `StopConsumerChain(chainId, false)` for any `chainId` MAY violate the *Bond-Based Consumer Voting Power* and *Slashable Consumer Misbehavior* properties (see the [System Properties](./system_model_and_properties#system-properties) section).
+>
+> `StopConsumerChain(chainId, false)` is invoked in two scenarios (see Trigger Event above).
+>
+> - In the first scenario (i.e., a governance proposal to stop the consumer chain with `chainId`), the validators on the provider chain MUST make sure that it is safe to stop the consumer chain.
+> Since a governance proposal needs a majority of the voting power to pass, the safety of invoking `StopConsumerChain(chainId, false)` is ensured by the *Safe Blockchain* assumption (see the [Assumptions](./system_model_and_properties#assumptions) section).
+>
+> - The second scenario (i.e., a timeout) is only possible if the *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section),
+> which is necessary to guarantee both the *Bond-Based Consumer Voting Power* and *Slashable Consumer Misbehavior* properties (see the [Assumptions](./system_model_and_properties#correctness-reasoning) section).
+
+{/* omit in toc */}
+#### **[CCV-PCF-EBLOCK-CCR.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function EndBlockCCR() {
+ // iterate over vscSendTimestamps
+ for (chainId, vscId) IN vscSendTimestamps.Keys() {
+ // check get first timestamp, i.e., the smallest
+ if currentTimestamp() > vscSendTimestamps[(chainId, vscId)] + vscTimeout {
+ // vscTimeout expired:
+ // stop the consumer chain and use lockUnbondingOnTimeout
+ // to decide whether to lock the unbonding
+ StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
+ }
+ }
+
+ // iterate over initTimeoutTimestamps
+ for chainId IN initTimeoutTimestamps.Keys() {
+ if currentTimestamp() > initTimeoutTimestamps[chainId] {
+ // initTimeout expired:
+ // stop the consumer chain and unlock the unbonding
+ StopConsumerChain(chainId, false)
+ }
+ }
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - For each consumer chain ID `chainId` in `vscSendTimestamps.Keys()`,
+ - if `vscSendTimestamps[(chainId, vscId)] + vscTimeout` is smaller than the current timestamp, then the consumer chain with ID `chainId` is stopped.
+ - For each consumer chain ID `chainId` in `initTimeoutTimestamps.Keys()`,
+ - if the timestamp in `initTimeoutTimestamps[chainId]` is smaller than the current timestamp, then the consumer chain with ID `chainId` is stopped.
+- **Error Condition**
+ - None.
+
+> **Note**: To avoid false positives where a consumer chain is unnecessarily removed,
+> `vscTimeout` MUST be larger than `consumerUnbondingPeriod` and
+> SHOULD account for the time needed to relay the `VSCPacket` to the consumer and the corresponding `VSCMaturedPacket` back to the provider.
+
+{/* omit in toc */}
+#### **[CCV-PCF-CCINIT.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // Disallow user-initiated channel closing
+ abortTransactionUnless(FALSE)
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanCloseInit` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is always aborted; hence, the state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-CCCONFIRM.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // do nothing
+}
+```
+
+- **Caller**
+ - The provider IBC routing module.
+- **Trigger Event**
+ - The provider IBC routing module receives a `ChanCloseConfirm` message on a port the provider CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - None.
+
+---
+
+{/* omit in toc */}
+#### **[CCV-CCF-BBLOCK-CCR.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function BeginBlockCCR() {
+ if providerChannel != "" AND channelKeeper.GetChannelState(providerChannel) == CLOSED {
+ // the CCV channel was established, but it was then closed;
+ // the consumer chain is no longer safe
+
+ // cleanup state, e.g.,
+ // providerChannel = ""
+
+ // shut down consumer chain
+ abortSystemUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The `BeginBlock()` method.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the CCV was established, but then was moved to the `CLOSED` state, then the state of the consumer CCV module is cleaned up, e.g., the `providerChannel` is unset.
+- **Error Condition**
+ - If the CCV was established, but then was moved to the `CLOSED` state.
+
+> **Note**: Once the CCV channel is closed, the provider chain can no longer provider security. As a result, the consumer chain MUST be shut down.
+> For an example of how to do this in practice, see the Cosmos SDK [implementation](https://github.com/cosmos/cosmos-sdk/blob/0c0b4da114cf73ef5ae1ac5268241d69e8595a60/x/upgrade/abci.go#L71).
+
+{/* omit in toc */}
+#### **[CCV-CCF-CCINIT.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // allow relayers to close duplicate OPEN channels,
+ // if the provider channel has already been established
+ if providerChannel == "" || providerChannel == channelIdentifier {
+ // user cannot close channel
+ abortTransactionUnless(FALSE)
+ }
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanCloseInit` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If `providerChannel` is not set or `providerChannel` matches the ID of the channel the `ChanCloseInit` message was received on, then the transaction is aborted.
+ - The state is not changed.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-CCCONFIRM.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// implements the ModuleCallbacks interface defined in ICS26
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // do nothing
+}
+```
+
+- **Caller**
+ - The consumer IBC routing module.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `ChanCloseConfirm` message on a port the consumer CCV module is bounded to.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - None.
+
+### Validator Set Update
+
+[↑ Back to Outline](#outline)
+
+The *validator set update* sub-protocol enables the provider chain
+
+- to update the consumer chain on the voting power granted to validators on the provider chain
+- and to ensure the correct completion of unbonding operations for validators that produce blocks on the consumer chain.
+
+{/* omit in toc */}
+#### **[CCV-PCF-EBLOCK-VSU.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function EndBlockVSU() {
+ // notify the Staking module to complete all matured unbondings
+ for id IN maturedUnbondingOps {
+ stakingKeeper.UnbondingCanComplete(id)
+ }
+ maturedUnbondingOps.RemoveAll()
+
+ // get list of validator updates from the provider Staking module
+ valUpdates = stakingKeeper.GetValidatorUpdates()
+
+ // iterate over all consumer chains registered with this provider chain
+ foreach chainId IN chainToClient.Keys() {
+ // check whether there are changes in the validator set;
+ // note that this also entails unbonding operations
+ // w/o changes in the voting power of the validators in the validator set
+ if len(valUpdates) != 0 OR len(vscToUnbondingOps[(chainId, vscId)]) != 0 {
+ // create VSCPacket data
+ data = VSCPacketData{
+ id: vscId,
+ updates: valUpdates,
+ downtimeSlashAcks: downtimeSlashRequests[chainId]
+ }
+ downtimeSlashRequests.Remove(chainId)
+
+ // add VSCPacket data to the list of pending VSCPackets
+ pendingVSCPackets.Append(chainId, data)
+ }
+
+ // check whether there is an established CCV channel to the consumer chain
+ if chainId IN chainToChannel.Keys() {
+ // get the channel ID for the given consumer chain ID
+ channelId = chainToChannel[chainId]
+
+ foreach data IN pendingVSCPackets[chainId] {
+ // send data using the interface exposed by ICS-4
+ channelKeeper.sendPacket(
+ portKeeper.getCapability(portKeeper.portPath(ProviderPortId)),
+ ProviderPortId, // source port ID
+ channelId, // source channel ID
+ zeroTimeoutHeight,
+ ccvTimeoutTimestamp,
+ data
+ )
+ // add VSC send timestamp to vscSendTimestamps
+ vscSendTimestamps[(vscId, chainId)] = currentTimestamp()
+ }
+
+ // remove pending VSCPackets
+ pendingVSCPackets.Remove(chainId)
+ }
+ }
+ // increment VSC ID
+ vscId++
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - For every matured unbonding operation in `maturedUnbondingOps`, the Staking module is notified that the unbonding can complete.
+ - All unbonding operation in `maturedUnbondingOps` are removed.
+ - A list of validator updates `valUpdates` is obtained from the provider Staking module.
+ - For every consumer chain with `chainId`
+ - If either `valUpdates` is not empty or there were unbonding operations initiated during this block, then
+ - a `VSCPacket` data `data` is created such that `data.id = vscId`, `data.updates = valUpdates`, and `data.downtimeSlashAcks = downtimeSlashRequests[chainId]`;
+ - `downtimeSlashRequests[chainId]` is emptied;
+ - `packetData` is appended to the list of pending `VSCPacket`s associated to `chainId`, i.e., `pendingVSCPackets[chainId]`.
+ - If there is an established CCV channel for the consumer chain with `chainId`, then
+ - for each `VSCPacketData` in the list of pending VSCPackets associated to `chainId`
+ - a packet with the `VSCPacketData` is sent on the channel associated with the consumer chain with `chainId`;
+ - `vscSendTimestamps[(vscId, chainId)]` is set to the current timestamp;
+ - all the pending VSCPackets associated to `chainId` are removed.
+ - `vscId` is incremented.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-ACKVSC.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function onAcknowledgeVSCPacket(packet: Packet, ack: bytes) {
+ // providing the VSC with id packet.data.id can fail,
+ // i.e., ack == VSCPacketError,
+ // only if the VSCPacket was sent on a channel
+ // other than the established CCV channel;
+ // that should never happen, see EndBlock()
+ abortSystemUnless(ack != VSCPacketError)
+}
+```
+
+- **Caller**
+ - The `onAcknowledgePacket()` method.
+- **Trigger Event**
+ - The provider IBC routing module receives an acknowledgement of a `VSCPacket` on a channel owned by the provider CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - The acknowledgement is `VSCPacketError`.
+
+{/* omit in toc */}
+#### **[CCV-PCF-TOVSC.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function onTimeoutVSCPacket(packet: Packet) {
+ // cleanup state
+ abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
+ chainId = channelToChain[packet.getDestinationChannel()]
+ // stop the consumer chain and use lockUnbondingOnTimeout
+ // to decide whether to lock the unbonding
+ StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])
+}
+```
+
+- **Caller**
+ - The `onTimeoutPacket()` method.
+- **Trigger Event**
+ - A `VSCPacket` sent on a channel owned by the provider CCV module timed out as a result of either
+ - the timeout height or timeout timestamp passing on the consumer chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end));
+ - or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)).
+- **Precondition**
+ - The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section).
+- **Postcondition**
+ - The transaction is aborted if the ID of the channel on which the packet was sent is not mapped to a chain ID (in `channelToChain`).
+ - `StopConsumerChain(chainId, lockUnbondingOnTimeout[chainId])` is invoked, where `chainId = channelToChain[packet.getDestinationChannel()]`.
+- **Error Condition**
+ - None
+
+{/* omit in toc */}
+#### **[CCV-PCF-RCVMAT.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function onRecvVSCMaturedPacket(packet: Packet): bytes {
+ // get the ID of the consumer chain mapped to this channel ID
+ abortTransactionUnless(packet.getDestinationChannel() IN channelToChain.Keys())
+ chainId = channelToChain[packet.getDestinationChannel()]
+
+ // iterate over the unbonding operations mapped to
+ // this chainId and vscId (i.e., packet.data.id)
+ foreach op in GetUnbondingsFromVSC(chainId, packet.data.id) {
+ // remove the consumer chain from
+ // the list of consumer chain that are still unbonding
+ op.unbondingChainIds.Remove(chainId)
+ // if the unbonding operation has unbonded on all consumer chains
+ if op.unbondingChainIds.IsEmpty() {
+ // append the id of the unbonding to maturedUnbondingOps
+ maturedUnbondingOps.Append(op.id)
+ // remove unbonding operation
+ unbondingOps.Remove(op.id)
+ }
+ }
+ // clean up vscToUnbondingOps mapping
+ vscToUnbondingOps.Remove((chainId, vscId))
+
+ // clean up vscSendTimestamps mapping
+ vscSendTimestamps.Remove((chainId, vscId))
+
+ return VSCMaturedPacketSuccess
+}
+```
+
+- **Caller**
+ - The `onRecvPacket()` method.
+- **Trigger Event**
+ - The provider IBC routing module receives a `VSCMaturedPacket` on a channel owned by the provider CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The transaction is aborted if the channel on which the packet was received is not an established CCV channel (i.e., not in `channelToChain`).
+ - `chainId` is set to the ID of the consumer chain mapped to the channel on which the packet was received.
+ - For each unbonding operation `op` returned by `GetUnbondingsFromVSC(chainId, packet.data.id)`
+ - `chainId` is removed from `op.unbondingChainIds`;
+ - if `op.unbondingChainIds` is empty,
+ - `op.id` is added to `maturedUnbondingOps`;
+ - `op.id` is removed from `unbondingOps`.
+ - `(chainId, vscId)` is removed from `vscToUnbondingOps`.
+ - `(chainId, vscId)` is removed from `vscSendTimestamps`.
+ - A successful acknowledgment is returned.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-GETUBS.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// Utility method
+function GetUnbondingsFromVSC(
+ chainId: Identifier,
+ _vscId: uint64): [UnbondingOperation] {
+ // get all unbonding operations associated with (chainId, _vscId)
+ ops = []
+ foreach id in vscToUnbondingOps[(chainId, _vscId)] {
+ // get the unbonding operation with this ID
+ op = unbondingOps[id]
+ // append the operation to the list of operations to be returned
+ ops.Append(op)
+ }
+ return ops
+}
+```
+
+- **Caller**
+ - The `onRecvVSCMaturedPacket()` method.
+- **Trigger Event**
+ - The provider IBC routing module receives a `VSCMaturedPacket` on a channel owned by the provider CCV module.
+- **Precondition**
+ - The provider CCV module received a `VSCMaturedPacket` `P` from a consumer chain with ID `chainId`, such that `P.data.id == _vscId`.
+- **Postcondition**
+ - Return the list of unbonding operations mapped to `(chainId, _vscId)`.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-HOOK-AFUBOPCR.1]**
+
+```typescript
+// PCF: Provider Chain Function
+// implements a Staking module hook
+function AfterUnbondingInitiated(opId: uint64) {
+ // get the IDs of all consumer chains registered with this provider chain;
+ // note: this includes also consumer chains in the pre-CCV state
+ chainIds = chainToClient.Keys()
+ if len(chainIds) > 0 {
+ // create and store a new unbonding operation
+ unbondingOps[opId] = UnbondingOperation{
+ id: opId,
+ unbondingChainIds: chainIds
+ }
+ // add the unbonding operation id to vscToUnbondingOps
+ foreach chainId in chainIds {
+ vscToUnbondingOps[(chainId, vscId)].Append(opId)
+ }
+
+ // ask the Staking module to wait for this operation
+ // to reach maturity on the consumer chains
+ stakingKeeper.PutUnbondingOnHold(opId)
+ }
+}
+```
+
+- **Caller**
+ - The Staking module.
+- **Trigger Event**
+ - An unbonding operation with id `opId` is initiated.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `chainIds` is set to the list of all consumer chains registered with this provider chain, i.e., `chainToClient.Keys()`.
+ - If there is at least one consumer chain in `chainIds`, then
+ - an `UnbondingOperation` `op` is created and added to `unbondingOps`, such that `op.id = opId` and `op.unbondingChainIds = chainIds`.
+ - `opId` is appended to every list in `vscToUnbondingOps[(chainId, vscId)]`, where `chainId` is an ID of a consumer chains registered with this provider chain and `vscId` is the current VSC ID.
+ - the `PutUnbondingOnHold(opId)` of the Staking module is invoked.
+- **Error Condition**
+ - None.
+
+---
+
+{/* omit in toc */}
+#### **[CCV-CCF-RCVVSC.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function onRecvVSCPacket(packet: Packet): bytes {
+ // check whether the packet was sent on the CCV channel
+ if providerChannel != "" && providerChannel != packet.getDestinationChannel() {
+ // packet sent on a channel other than the established provider channel;
+ // return error acknowledgement
+ return VSCPacketError
+ }
+
+ // set HtoVSC mapping
+ HtoVSC[getCurrentHeight() + 1] = packet.data.id
+
+ // store the packet data
+ receivedVSCs.Append(packet.data)
+
+ return VSCPacketSuccess
+}
+```
+
+- **Caller**
+ - The `onRecvPacket()` method.
+- **Trigger Event**
+ - The consumer IBC routing module receives a `VSCPacket` on a channel owned by the consumer CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If `providerChannel` is set and does not match the channel (with ID `packet.getDestinationChannel()`) on which the packet was received, then an error acknowledgement is returned.
+ - Otherwise,
+ - the height of the subsequent block is mapped to `packet.data.id` (i.e., the `HtoVSC` mapping) ;
+ - `packet.data` is appended to `receivedVSCs`.
+ - a successful acknowledgement is returned.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-ACKMAT.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function onAcknowledgeVSCMaturedPacket(packet: Packet, ack: bytes) {
+ // notifications of VSC maturity cannot fail by construction
+ abortSystemUnless(ack != VSCMaturedPacketError)
+}
+```
+
+- **Caller**
+ - The `onAcknowledgePacket()` method.
+- **Trigger Event**
+ - The consumer IBC routing module receives an acknowledgement of a `VSCMaturedPacket` on a channel owned by the consumer CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - The acknowledgement is `VSCMaturedPacketError`.
+
+{/* omit in toc */}
+#### **[CCV-CCF-TOMAT.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function onTimeoutVSCMaturedPacket(packet Packet) {
+ // the CCV channel state is changed to CLOSED
+ // by the IBC handler (since the channel is ORDERED)
+}
+```
+
+- **Caller**
+ - The `onTimeoutPacket()` method.
+- **Trigger Event**
+ - A `VSCMaturedPacket` sent on a channel owned by the consumer CCV module timed out as a result of either
+ - the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end));
+ - or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)).
+- **Precondition**
+ - The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section).
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - None
+
+{/* omit in toc */}
+#### **[CCV-CCF-EBLOCK-VSU.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function EndBlockVSU(): [ValidatorUpdate] {
+ // unbond mature packets if the CCV channel is established
+ if providerChannel != "" {
+ UnbondMaturePackets()
+ }
+
+ if preCCV {
+ // do nothing
+ return []
+ }
+ else {
+ // handle received VSCs
+ changes = HandleReceivedVSCs()
+
+ // update ccvValidatorSet
+ UpdateValidatorSet(changes)
+
+ // return the validator set updates
+ return changes
+ }
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If `providerChannel != ""`, `UnbondMaturePackets()` is invoked;
+ - If `preCCV == true`, the state is not changed.
+ - Otherwise,
+ - the data items in `receivedVSCs` are handled (see [[CCV-CCF-HAREVSC.1]](#ccv-ccf-harevsc1)), which results in a list `changes` of validator updates;
+ - `UpdateValidatorSet(changes)` is invoked;
+ - `changes` is returned.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-HAREVSC.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function HandleReceivedVSCs(): [ValidatorUpdate] {
+ changes = []
+ foreach data IN receivedVSCs {
+ // store the list of updates
+ changes.Append(data.updates)
+
+ // calculate and store the maturity timestamp for the VSC
+ maturityTimestamp = currentTimestamp().Add(ConsumerUnbondingPeriod)
+ maturingVSCs.Add(data.id, maturityTimestamp)
+
+ // reset outstandingDowntime for validators in data.downtimeSlashAcks
+ foreach valAddr IN data.downtimeSlashAcks {
+ outstandingDowntime[valAddr] = FALSE
+ }
+ }
+ // remove all entries
+ receivedVSCs = []
+
+ // aggregate the updates,
+ // i.e., keep only the latest update per validator;
+ // note: in the implementation, the aggregation is done directly
+ // when receiving a VSCPacket via the AccumulateChanges method
+ return changes.Aggregate()
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine.
+- **Precondition**
+ - `preCCV == false`.
+- **Postcondition**
+ - For each `data` item in the list `receivedVSCs`,
+ - `data.updates` are appended to `changes`, where `changes` is initially an empty list of validator updates;
+ - `(data.id, maturityTimestamp)` is added to `maturingVSCs`, where `maturityTimestamp = currentTimestamp() + ConsumerUnbondingPeriod`;
+ - for each `valAddr` in the slash acknowledgments received from the provider chain, `outstandingDowntime[valAddr]` is set to false.
+ - `receivedVSCs` is emptied.
+ - The updates in `changes` are aggregated, i.e., only the latest update per validator is kept, and returned.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-UPVALS.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function UpdateValidatorSet(changes: [ValidatorUpdate]) {
+ foreach update IN changes {
+ addr := hash(update.pubKey)
+ if addr NOT IN ccvValidatorSet.Keys() {
+ // new validator bonded;
+ // note that due changes.Aggregate(),
+ // a validator can be added to the valset and
+ // then removed in the subsequent block,
+ // resulting in update.power == 0
+ if update.power > 0 {
+ // add new validator to validator set
+ ccvValidatorSet[addr] = update
+ // call AfterCCValidatorBonded hook
+ AfterCCValidatorBonded(addr)
+ }
+ }
+ else if update.power == 0 {
+ // existing validator begins unbonding
+ ccvValidatorSet.Remove(addr)
+ // call AfterCCValidatorBeginUnbonding hook
+ AfterCCValidatorBeginUnbonding(addr)
+ }
+ else {
+ ccvValidatorSet[addr].power = update.power
+ }
+ }
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine.
+- **Precondition**
+ - `preCCV == false`.
+- **Postcondition**
+ - For each validator `update` in `changes`,
+ - if the validator is not in the validator set and `update.power > 0`, then
+ - a new validator is added to `ccvValidatorSet`;
+ - the `AfterCCValidatorBonded` hook is called;
+ - otherwise, if the validator's new power is `0`, then,
+ - the validator is removed from `ccvValidatorSet`;
+ - the `AfterCCValidatorBeginUnbonding` hook is called;
+ - otherwise, the validator's power is updated.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-UMP.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function UnbondMaturePackets() {
+ foreach (id, ts) in maturingVSCs.SortedByMaturityTime() {
+ if currentTimestamp() < ts {
+ break // stop loop
+ }
+ // create VSCMaturedPacketData
+ packetData = VSCMaturedPacketData{id: id}
+
+ // send VSCMaturedPacketData using the interface exposed by ICS-4
+ channelKeeper.sendPacket(
+ portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
+ ConsumerPortId, // source port ID
+ providerChannel, // source channel ID
+ zeroTimeoutHeight,
+ ccvTimeoutTimestamp,
+ packetData
+ )
+
+ // remove entry from the list
+ maturingVSCs.Remove(id, ts)
+ }
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine.
+- **Precondition**
+ - The CCV channel to the provider chain is established, i.e., `providerChannel != ""`.
+- **Postcondition**
+ - For each `(id, ts)` in the list of maturing VSCs sorted by maturity timestamps
+ - if `currentTimestamp() < ts`, the loop is stopped;
+ - a `VSCMaturedPacketData` packet data is created;
+ - a packet with the created `VSCMaturedPacketData` is sent to the provider chain;
+ - the tuple `(id, ts)` is removed from `maturingVSCs`.
+- **Error Condition**
+ - None.
+
+### Consumer Initiated Slashing
+
+[↑ Back to Outline](#outline)
+
+{/* omit in toc */}
+#### **[CCV-PCF-EBLOCK-CIS.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function EndBlockCIS() {
+ // set VSCtoH mapping
+ VSCtoH[vscId] = getCurrentHeight() + 1
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `vscId` is mapped to the height of the subsequent block.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-PCF-RCVSLASH.1]**
+
+```typescript
+// PCF: Provider Chain Function
+function onRecvSlashPacket(packet: Packet): bytes {
+ // check whether the packet was received on an established CCV channel
+ if packet.getDestinationChannel() NOT IN channelToChain.Keys() {
+ // packet received on a non-established channel; incorrect behavior
+ return SlashPacketError
+ }
+
+ // get the height that maps to the VSC ID in the packet data
+ if packet.data.vscId == 0 {
+ // the infraction happened before sending any VSC to this chain
+ chainId = channelToChain[packet.getDestinationChannel()]
+ infractionHeight = initialHeights[chainId]
+ }
+ else {
+ infractionHeight = VSCtoH[packet.data.vscId]
+ }
+
+ // request the Slashing module to slash the validator
+ // using the slashFactor set on the provider chain
+ slashFactor = slashingKeeper.GetSlashFactor(packet.data.downtime)
+ slashingKeeper.Slash(
+ packet.data.valAddress,
+ infractionHeight,
+ packet.data.valPower,
+ slashFactor))
+
+ // request the Slashing module to jail the validator
+ // using the jailTime set on the provider chain
+ jailTime = slashingKeeper.GetJailTime(packet.data.downtime)
+ slashingKeeper.JailUntil(packet.data.valAddress, currentTimestamp() + jailTime)
+
+ if packet.data.downtime {
+ // add validator to list of downtime slash requests for chainId
+ downtimeSlashRequests[chainId].Append(packet.data.valAddress)
+ }
+
+ return SlashPacketSuccess
+}
+```
+
+- **Caller**
+ - The `onRecvPacket()` method.
+- **Trigger Event**
+ - The provider IBC routing module receives a `SlashPacket` on a channel owned by the provider CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the channel the packet was received on is not an established CCV channel, then an error acknowledgment is returned.
+ - Otherwise,
+ - if `packet.data.vscId == 0`, `infractionHeight` is set to `initialHeights[chainId]`, with `chainId = channelToChain[packet.getDestinationChannel()]`, i.e., the height when the CCV channel to this consumer chain is established;
+ - otherwise, `infractionHeight` is set to `VSCtoH[packet.data.vscId]`, i.e., the height at which the voting power was last updated by the validator updates in the VSC with ID `packet.data.vscId`;
+ - a request is made to the Slashing module to slash `slashFactor` of the tokens bonded at `infractionHeight` by the validator with address `packet.data.valAddress`, where `slashFactor` is the slashing factor set on the provider chain;
+ - a request is made to the Slashing module to jail the validator with address `packet.data.valAddress` for a period `jailTime`, where `jailTime` is the jailing time set on the provider chain;
+ - if the slash request is for downtime, the validator's address `packet.data.valAddress` is added to the list of downtime slash requests from this `chainId`;
+ - a successful acknowledgment is returned.
+- **Error Condition**
+ - None.
+
+---
+
+{/* omit in toc */}
+#### **[CCV-CCF-BBLOCK-CIS.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function BeginBlockCIS() {
+ HtoVSC[getCurrentHeight() + 1] = HtoVSC[getCurrentHeight()]
+}
+```
+
+- **Caller**
+ - The `BeginBlock()` method.
+- **Trigger Event**
+ - A `BeginBlock` message is received from the consensus engine; `BeginBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - `HtoVSC` for the subsequent block height is set to the same VSC ID as the current block height.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-ACKSLASH.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function onAcknowledgeSlashPacket(packet: Packet, ack: bytes) {
+ // slash request fail, i.e., ack == SlashPacketError,
+ // only if the SlashPacket was sent on a channel
+ // other than the established CCV channel;
+ // that should never happen,
+ // see SendSlashRequest() and SendPendingSlashRequests()
+ abortSystemUnless(ack != SlashPacketError)
+}
+```
+
+- **Caller**
+ - The `onAcknowledgePacket()` method.
+- **Trigger Event**
+ - The consumer IBC routing module receives an acknowledgement of a `SlashPacket` on a channel owned by the consumer CCV module.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - The acknowledgement is `SlashPacketError`.
+
+{/* omit in toc */}
+#### **[CCV-CCF-TOSLASH.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function onTimeoutSlashPacket(packet Packet) {
+ // the CCV channel state is changed to CLOSED
+ // by the IBC handler (since the channel is ORDERED)
+}
+```
+
+- **Caller**
+ - The `onTimeoutPacket()` method.
+- **Trigger Event**
+ - A `SlashPacket` sent on a channel owned by the consumer CCV module timed out as a result of either
+ - the timeout height or timeout timestamp passing on the provider chain without the packet being received (see `timeoutPacket` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#sending-end));
+ - or the channel being closed without the packet being received (see `timeoutOnClose` defined in [ICS4](../../core/ics-004-channel-and-packet-semantics/README#timing-out-on-close)).
+- **Precondition**
+ - The *Correct Relayer* assumption is violated (see the [Assumptions](./system_model_and_properties#assumptions) section).
+- **Postcondition**
+ - The state is not changed.
+- **Error Condition**
+ - None
+
+{/* omit in toc */}
+#### **[CCV-CCF-SNDSLASH.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// Enables consumer initiated slashing
+function SendSlashRequest(
+ valAddress: string,
+ power: int64,
+ infractionHeight: Height,
+ downtime: Bool) {
+ if downtime AND outstandingDowntime[data.valAddress] {
+ // do not send multiple requests for the same downtime
+ return
+ }
+
+ // create SlashPacket data
+ packetData = SlashPacketData{
+ valAddress: valAddress,
+ valPower: power,
+ vscId: HtoVSC[infractionHeight],
+ downtime: downtime
+ }
+
+ // check whether the CCV channel to the provider chain is established
+ if providerChannel != "" {
+ // send SlashPacket data using the interface exposed by ICS-4
+ channelKeeper.sendPacket(
+ portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
+ ConsumerPortId, // source port ID
+ providerChannel, // source channel ID
+ zeroTimeoutHeight,
+ ccvTimeoutTimestamp,
+ packetData
+ )
+
+ if downtime {
+ // set outstandingDowntime for this validator
+ outstandingDowntime[data.valAddress] = TRUE
+ }
+ }
+ else {
+ // add SlashPacket data to the list of pending SlashPackets
+ req := SlashRequest{data: packetData, downtime: downtime}
+ pendingSlashRequests.Append(req)
+ }
+}
+```
+
+- **Caller**
+ - The ABCI application (e.g., the Slashing module).
+- **Trigger Event**
+ - Evidence of misbehavior for a validator with address `valAddress` was received.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If the request is for downtime and there is an outstanding request to slash this validator for downtime, then the state is not changed.
+ - Otherwise,
+ - a `SlashPacket` data `packetData` is created, such that `packetData.vscId = VSCtoH[infractionHeight]`;
+ - if the CCV channel to the provider chain is established, then
+ - a packet with the `packetData` is sent to the provider chain;
+ - if the request is for downtime, `outstandingDowntime[data.valAddress]` is set to true;
+ - otherwise `SlashRequest{data: packetData, downtime: downtime}` is appended to `pendingSlashRequests`.
+- **Error Condition**
+ - None.
+
+> **Note**: The ABCI application MUST subtract `ValidatorUpdateDelay` from the infraction height before invoking `SendSlashRequest`,
+> where `ValidatorUpdateDelay` is a delay (in blocks) between when validator updates are returned to the consensus-engine and when they are applied.
+> For example, if `ValidatorUpdateDelay = x` and a validator set update is returned with new validators at the end of block `10`,
+> then the new validators are expected to sign blocks beginning at block `11+x`
+> (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#endblock)).
+>
+> Consequently, the consumer CCV module expects the `infractionHeight` parameter of the `SendSlashRequest()` to be set accordingly.
+>
+> **Note**: In the context of single-chain validation, slashing for downtime is an ***atomic operation***, i.e., once the downtime is detected, the misbehaving validator is slashed and jailed immediately.
+> Consequently, once a validator is punished for downtime, it is removed from the validator set and cannot be punished again for downtime.
+> Since validators are not automatically added back to the validator set, it entails that the validator is aware of the punishment before it can rejoin and be potentially punished again.
+>
+> In the context of CCV, slashing for downtime is no longer atomic, i.e., downtime is detected on the consumer chain, but the jailing happens on the provider chain.
+> To avoid sending multiple slash requests for the same downtime infraction, the consumer CCV module uses an `outstandingDowntime` flag per validator.
+> CCV assumes that the consumer ABCI application (e.g., the slashing module) is not including the downtime of a validator with `outstandingDowntime == TRUE` in the evidence for downtime.
+
+{/* omit in toc */}
+#### **[CCV-CCF-SNDPESLASH.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+// Utility method
+function SendPendingSlashRequests() {
+ // iterate over every pending SlashRequest in reverse order
+ foreach req IN pendingSlashRequests.Reverse() {
+ if !req.downtime OR !outstandingDowntime[req.data.valAddress] {
+ // send req.data using the interface exposed by ICS-4
+ channelKeeper.sendPacket(
+ portKeeper.getCapability(portKeeper.portPath(ConsumerPortId)),
+ ConsumerPortId, // source port ID
+ providerChannel, // source channel ID
+ zeroTimeoutHeight,
+ ccvTimeoutTimestamp,
+ req.data
+ )
+
+ if req.downtime {
+ // set outstandingDowntime for this validator
+ outstandingDowntime[req.data.valAddress] = TRUE
+ }
+ }
+ }
+ // remove pending SlashRequest
+ pendingSlashRequests.RemoveAll()
+}
+```
+
+- **Caller**
+ - The `onRecvVSCPacket()` method (see [CCV-CCF-RCVVSC.1](#ccv-ccf-rcvvsc1)).
+- **Trigger Event**
+ - The first `VSCPacket` is received from the provider chain.
+- **Precondition**
+ - `providerChannel != ""`.
+- **Postcondition**
+ - For each slash request `req` in `pendingSlashRequests` in reverse order, such that either the slash request is not for downtime or there is no outstanding slash request for downtime,
+ - a packet with the data `req.data` is sent to the provider chain;
+ - if the request is for downtime, `outstandingDowntime[req.data.valAddress]` is set to true.
+ - All the pending `SlashRequest`s are removed.
+- **Error Condition**
+ - None.
+
+> **Note**: Iterating over pending `SlashRequest`s in reverse order ensures that validators that are down for multiple blocks during channel initialization will be slashed for the latest downtime evidence.
+
+### Reward Distribution
+
+[↑ Back to Outline](#outline)
+
+{/* omit in toc */}
+#### **[CCV-CCF-EBLOCK-RD.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function EndBlockRD() {
+ if getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer {
+ DistributeRewards()
+ }
+}
+```
+
+- **Caller**
+ - The `EndBlock()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine; `EndBlock` messages are sent once per block.
+- **Precondition**
+ - True.
+- **Postcondition**
+ - If `getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer`, the `DistributeRewards()` method is invoked.
+- **Error Condition**
+ - None.
+
+{/* omit in toc */}
+#### **[CCV-CCF-DISTRREW.1]**
+
+```typescript
+// CCF: Consumer Chain Function
+function DistributeRewards() {
+ // iterate over all different tokens in ccvAccount
+ foreach (denomination, amount) IN ccvAccount.GetAllBalances() {
+ // transfer token using ICS20
+ transferKeeper.sendFungibleTokens(
+ denomination,
+ amount,
+ ccvAccount, // sender
+ providerDistributionAccount, // receiver
+ "transfer", // transfer port
+ distributionChannelId, // transfer channel ID
+ zeroTimeoutHeight, // timeoutHeight
+ transferTimeoutTimestamp // timeoutTimestamp
+ )
+ }
+ lastDistributionTransferHeight = getCurrentHeight()
+}
+```
+
+- **Caller**
+ - The `EndBlockRD()` method.
+- **Trigger Event**
+ - An `EndBlock` message is received from the consensus engine.
+- **Precondition**
+ - `getCurrentHeight() - lastDistributionTransferHeight >= BlocksPerDistributionTransfer`
+- **Postcondition**
+ - For each token type defined as a pair `(denomination, amount)` in `ccvAccount`, a transfer token (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)) is initiated.
+ - `lastDistributionTransferHeight` is set to the current height.
+- **Error Condition**
+ - None.
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.mdx
new file mode 100644
index 00000000..617ad4b9
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/overview_and_basic_concepts.mdx
@@ -0,0 +1,351 @@
+---
+title: "CCV: Overview and Basic Concepts"
+---
+
+## Outline
+
+- [Security Model](#security-model)
+- [Motivation](#motivation)
+- [Definition](#definition)
+- [Overview](#overview)
+ - [Channel Initialization](#channel-initialization)
+ - [Channel Initialization: New Chains](#channel-initialization-new-chains)
+ - [Channel Initialization: Existing Chains](#channel-initialization-existing-chains)
+ - [Validator Set Update](#validator-set-update)
+ - [Completion of Unbonding Operations](#completion-of-unbonding-operations)
+ - [Consumer Initiated Slashing](#consumer-initiated-slashing)
+ - [Reward Distribution](#reward-distribution)
+
+## Security Model
+
+[↑ Back to Outline](#outline)
+
+We consider chains that use a proof of stake mechanism based on the model of [weak subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/)
+in order to strengthen the assumptions required by the underlying consensus engine
+(e.g., [Tendermint](https://arxiv.org/pdf/1807.04938.pdf) requires that less than a third of the voting power is Byzantine).
+
+> **Background**: The next block in a blockchain is *validated* and *voted* upon by a set of pre-determined *full nodes*; these pre-determined full nodes are also known as *validators*.
+We refer to the validators eligible to validate a block as that block's *validator set*.
+To be part of the validator set, a validator needs to *bond* (i.e., lock, stake) an amount of tokens for a (minimum) period of time, known as the *unbonding period*.
+The amount of tokens bonded gives a validator's *voting power*.
+When a validator starts unbonding some of its tokens, its voting power is reduced immediately,
+but the tokens are unbonded (i.e., unlocked) only after the unbonding period has elapsed.
+If a validator misbehaves (e.g., validates two different blocks at the same height), then the system can slash the validator's bonded tokens that gave its voting power during the misbehavior.
+This prevents validators from misbehaving and immediately exiting with their tokens,
+i.e., the unbonding period enables the system to punish misbehaving validators after the misbehaviors are committed.
+For more details, take a look at the [Tendermint Specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/core/data_structures.md)
+and the [Light Client Specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/light-client/verification/verification_002_draft.md#part-i---tendermint-blockchain).
+
+In the context of CCV, the validator sets of the consumer chains are chosen based on the tokens validators bonded on the provider chain,
+i.e., are chosen from the validator set of the provider chain.
+When validators misbehave on the consumer chains, their tokens bonded on the provider chain are slashed.
+As a result, the security gained from the value of the tokens bonded on the provider chain is shared with the consumer chains.
+
+Similarly to the single-chain approach, when a validator starts unbonding some of its bonded tokens, its voting power is reduced on all chains (i.e., provider chain and consumer chains);
+yet, due to delays in the communication over the IBC protocol (e.g., due to relaying packets), the voting power is not reduced immediately on the consumer chains.
+A further consequence of CCV is that the tokens are unbonded only after the unbonding period has elapsed on all chains starting from the moment the corresponding voting power was reduced.
+Thus, CCV may delay the unbonding of tokens validators bonded on the provider chain.
+
+## Motivation
+
+[↑ Back to Outline](#outline)
+
+CCV is a primitive (i.e., a building block) that enables arbitrary shared security models: The security of a chain can be composed of security transferred from multiple provider chains including the chain itself (a consumer chain can be its own provider). As a result, CCV enables chains to borrow security from more established chains (e.g., Cosmos Hub), in order to boost their own security, i.e., increase the cost of attacking their networks.
+> **Intuition**: For example, for chains based on Tendermint consensus, a variety of attacks against the network are possible if an attacker acquire 1/3+ or 2/3+ of all bonded tokens. Since the market cap of newly created chains could be relatively low, an attacker could realistically acquire sufficient tokens to pass these thresholds. As a solution, CCV allows the newly created chains to use validators that have stake on chains with a much larger market cap and, as a result, increase the cost an attacker would have to pay.
+
+Moreover, CCV enables *hub minimalism*. In a nutshell, hub minimalism entails keeping a hub in the Cosmos network (e.g., the Cosmos Hub) as simple as possible, with as few features as possible in order to decrease the attack surface. CCV enables moving distinct features (e.g., DEX) to independent chains that are validated by the same set of validators as the hub.
+
+> **Versioning**: Note that CCV will be developed progressively.
+> This standard document specifies the V1 release, which will require the validator set of a consumer chain to be entirely provided by the provider chain.
+> In other words, once a provider chain agrees to provide security to a consumer chain, the entire validator set of the provider chain MUST validate also on the consumer chain.
+>
+> For more details on the planned releases, take a look at the [Interchain Security light paper](https://cosmos.github.io/interchain-security/introduction/overview).
+
+## Definition
+
+[↑ Back to Outline](#outline)
+
+This section defines the new terms and concepts introduced by CCV.
+
+- **Provider Chain**: The blockchain that provides security, i.e., manages the validator set of the consumer chain.
+
+- **Consumer Chain**: The blockchain that consumes security, i.e., enables the provider chain to manage its validator set.
+
+> **Note**: In this specification, the validator set of the consumer chain is entirely provided by the provider chain.
+
+Both the provider and the consumer chains are [application-specific blockchains](/sdk/v0.53/learn/intro/why-app-specific),
+i.e., each blockchain's state machine is typically connected to the underlying consensus engine via a *blockchain interface*, such as [ABCI](https://github.com/tendermint/spec/tree/v0.7.1/spec/abci).
+The blockchain interface MUST enable the state machine to provide to the underlying consensus engine a set of validator updates, i.e., changes in the voting power granted to validators.
+Although this specification is not dependent on ABCI, for ease of presentation, we refer to the state machines as ABCI applications.
+Also, this specification considers a modular paradigm,
+i.e., the functionality of each ABCI application is separated into multiple modules, like the approach adopted by [Cosmos SDK](/sdk/v0.53/learn/beginner/app-anatomy#modules).
+
+- **CCV Module**: The module that implements the CCV protocol. Both the provider and the consumer chains have each their own CCV module.
+Furthermore, the functionalities provided by the CCV module differ between the provider chain and the consumer chains.
+For brevity, we use *provider CCV module* and *consumer CCV module* to refer to the CCV modules on the provider chain and on the consumer chains, respectively.
+
+- **CCV Channel**: A unique, ordered IBC channel that is used by the provider CCV module to exchange IBC packets with a consumer CCV module.
+Note that there is a separate CCV channel for every consumer chain.
+
+The IBC handler interface, the IBC relayer module interface, and both IBC channels and IBC packets are as defined in [ICS 25](../../core/ics-025-handler-interface), [ICS 26](../../core/ics-026-routing-module), and [ICS 4](../../core/ics-004-channel-and-packet-semantics), respectively.
+
+- **Validator Set Change (VSC)**: A change in the validator set of the provider chain that must be reflected in the validator sets of the consumer chains.
+Every VSC consists of a batch of validator updates provided to the consensus engine of the provider chain.
+
+> **Background**: In the context of single-chain validation, the changes of the validator set are triggered by the *Staking module*,
+> i.e., a module of the ABCI application that implements the proof of stake mechanism needed by the [security model](#security-model).
+> For an example, take a look at the [Staking module documentation](/sdk/v0.53/build/modules/staking) of Cosmos SDK.
+
+Some of the validator updates can decrease the voting power granted to validators.
+These decreases may be a consequence of unbonding operations (e.g., unbonding delegations) on the provider chain.
+which MUST NOT complete before reaching maturity on both the provider and all the consumer chains,
+i.e., the *unbonding period* (denoted as `UnbondingPeriod`) has elapsed on both the provider and all the consumer chains.
+Thus, a *VSC reaching maturity* on a consumer chain means that all the unbonding operations that resulted in validator updates included in that VSC have matured on the consumer chain.
+
+> **Background**: An *unbonding operation* is any operation of unbonding an amount of the tokens a validator bonded. Note that the bonded tokens correspond to the validator's voting power. We distinguish between three types of unbonding operations:
+>
+> - *undelegation* - a delegator unbonds tokens it previously delegated to a validator;
+> - *redelegation* - a delegator instantly redelegates tokens from a source validator to a different validator (the destination validator);
+> - *validator unbonding* - a validator is removed from the validator set; note that although validator unbondings do not entail unbonding tokens, they behave similarly to other unbonding operations.
+>
+> Regardless of the type, unbonding operations have two components:
+>
+> - The *initiation*, e.g., a delegator requests their delegated tokens to be unbonded. The initiation of an operation of unbonding an amount of the tokens a validator bonded results in a change in the voting power of that validator.
+> - The *completion*, e.g., the tokens are actually unbonded and transferred back to the delegator. To complete, unbonding operations must reach *maturity*, i.e., `UnbondingPeriod` must elapse since the operations were initiated.
+>
+> For more details, take a look at the [Cosmos SDK documentation](/sdk/v0.53/build/modules/staking).
+>
+> **Note**: Time periods are measured in terms of the block time, i.e., `currentTimestamp()` (as defined in [ICS 24](../../core/ics-024-host-requirements)).
+> As a result, a consumer chain MAY start the unbonding period for every VSC that it applies in a block at any point during that block.
+
+- **Slash Request**: A request by a consumer chain to *slash* the tokens bonded by a validator on the provider chain as a consequence of that validator misbehavior on the consumer chain. A slash request MAY also result in the misbehaving validator being *jailed* for a period of time, during which it cannot be part of the validator set.
+
+> **Background**: In the context of single-chain validation, slashing and jailing misbehaving validators is handled by the *Slashing module*,
+> i.e., a module of the ABCI application that enables the application to discourage misbehaving validators.
+> For an example, take a look at the [Slashing module documentation](/sdk/v0.53/build/modules/slashing) of Cosmos SDK.
+
+## Overview
+
+[↑ Back to Outline](#outline)
+
+CCV must handle the following types of operations:
+
+- **Channel Initialization**: Create unique, ordered IBC channels between the provider chain and every consumer chain.
+- **Validator Set Update**: It is a two-part operation, i.e.,
+ - update the validator sets of all the consumer chains based on the information obtained from the *provider Staking module* (i.e., the Staking module on the provider chain) on the amount of tokens bonded by validators on the provider chain;
+ - and enable the timely completion (cf. the unbonding periods on the consumer chains) of unbonding operations (i.e., operations of unbonding bonded tokens).
+- **Consumer Initiated Slashing**: Enable the provider chain to slash and jail bonded validators that misbehave while validating on the consumer chain.
+- **Reward Distribution**: Enable the distribution of block production rewards and transaction fees from the consumer chains to the validators on the provider chain.
+
+### Channel Initialization
+
+[↑ Back to Outline](#outline)
+
+The CCV Channel initialization differentiates between chains that start directly as consumer chains and existing chains that transition to consumer chains.
+In both cases, consumer chains are created through governance proposals. For an example of how governance proposals work, take a look at the [Governance module documentation](/sdk/v0.53/build/modules/gov) of Cosmos SDK.
+
+#### Channel Initialization: New Chains
+
+The following figure shows an overview of the CCV Channel initialization for new chains.
+
+
+
+The channel initialization for new chains consists of three phases:
+
+- **Create clients**: Once the provider CCV module receives a proposal to add a new consumer chain with an empty connection ID, it creates a client of the consumer chain (as defined in [ICS 2](../../core/ics-002-client-semantics)) and a genesis state of the consumer CCV module.
+ Then, the operators of validators in the validator set of the provider chain must each query the provider for the CCV genesis state and start a validator node of the consumer chain.
+ Once the consumer chain starts, the application receives an `InitChain` message from the consensus engine
+ (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/blob/v0.7.1/spec/abci/abci.md#initchain)).
+ The `InitChain` message triggers the call to the `InitGenesis()` method of the consumer CCV module, which creates a client of the provider chain.
+ For client creation, both a `ClientState` and a `ConsensusState` are necessary (as defined in [ICS 2](../../core/ics-002-client-semantics));
+ both are contained in the genesis state of the consumer CCV module.
+ The genesis state is distributed to all operators that need to start a full node of the consumer chain
+ (the mechanism of distributing the genesis state is outside the scope of this specification).
+ Finally, the consumer CCV module initiates both the connection handshake (as defined in [ICS 3](../../core/ics-003-connection-semantics)) and the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+ > Note that at genesis, the validator set of the consumer chain matches the validator set of the provider chain.
+- **Connection handshake**: A relayer (as defined in [ICS 18](../../relayer/ics-018-relayer-algorithms)) is responsible for completing the connection handshake (as defined in [ICS 3](../../core/ics-003-connection-semantics)).
+- **Channel handshake**: A relayer is responsible for completing the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+ The handshake consists of four messages that need to be received for a channel built on top of the expected clients.
+ Note that the channel handshake is initiated on the consumer chain.
+ - *OnChanOpenInit*: On receiving a `ChanOpenInit` message, the consumer CCV module verifies that the underlying client associated with this channel is the expected client of the provider chain (i.e., created during genesis).
+ - *OnChanOpenTry*: On receiving a `ChanOpenTry` message, the provider CCV module verifies that the underlying client associated with this channel is the expected client of the consumer chain (i.e., created when handling the governance proposal).
+ - *OnChanOpenAck*: On receiving the *FIRST* `ChanOpenAck` message, the consumer CCV module considers its side of the CCV channel to be established.
+ Also, if a transfer channel ID was not provided in the governance proposal, the consumer CCV module initiates the opening handshake for the token transfer channel required by the Reward Distribution operation (see the [Reward Distribution](#reward-distribution) section).
+ - *OnChanOpenConfirm*: On receiving the *FIRST* `ChanOpenConfirm` message, the provider CCV module considers its side of the CCV channel to be established.
+
+#### Channel Initialization: Existing Chains
+
+The following figure shows an overview of the CCV Channel initialization for existing chains.
+
+
+
+The channel initialization for existing chains consists of three phases:
+
+- **Start consumer CCV module**: Once the provider CCV module receives a proposal to add a new consumer chain with a valid *connection ID*, it creates a genesis state of the consumer CCV module.
+ Then, the existing chain must upgrade by adding the consumer CCV module and initialize it using the CCV genesis state created by the provider.
+ Once the consumer CCV module starts, it initiates the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+
+- **Channel handshake**: A relayer is responsible for completing the channel handshake (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+ The handshake consists of four messages that need to be received for a channel built on top of the expected clients (i.e., the clients used by the connection provided in the governance proposal).
+ Note that the channel handshake is initiated on the consumer chain.
+ - *OnChanOpenInit*: On receiving a `ChanOpenInit` message, the consumer CCV module verifies that the underlying client associated with this channel is the expected client of the provider chain.
+ - *OnChanOpenTry*: On receiving a `ChanOpenTry` message, the provider CCV module verifies that the underlying client associated with this channel is the expected client of the consumer chain.
+ - *OnChanOpenAck*: On receiving the *FIRST* `ChanOpenAck` message, the consumer CCV module considers its side of the CCV channel to be established.
+ Also, if a transfer channel ID was not provided in the governance proposal, the consumer CCV module initiates the opening handshake for the token transfer channel required by the Reward Distribution operation (see the [Reward Distribution](#reward-distribution) section).
+ Finally, the consumer CCV module makes a requests to the Staking module of the existing chain to replace its validator set with the initial validator set from the CCV genesis state created by the provider.
+ > Note that this is the same as the provider validator set when the governance proposal was handled.
+ - *OnChanOpenConfirm*: On receiving the *FIRST* `ChanOpenConfirm` message, the provider CCV module considers its side of the CCV channel to be established.
+
+- **Transition to consumer chain**: Once the validator set on the existing chain is replace by the initial validator set (from the CCV genesis state created by the provider), the existing chain becomes a consumer chain.
+
+> **Note**: For both new and existing chains, as long as the [assumptions required by CCV](./system_model_and_properties#assumptions) hold (e.g., *Correct Relayer*), every governance proposal to spawn a new consumer chain that passes on the provider chain results eventually in a CCV channel being created.
+> Furthermore, the "*FIRST*" keyword in the above description ensures the uniqueness of the CCV channel, i.e., all subsequent attempts to create another CCV channel to the same consumer chain will fail.
+>
+> **Note**: For both new and existing chains, until the CCV channel is established, the initial validator set of the consumer chain cannot be updated (see the [Validator Set Update](#validator-set-update) section) and the validators from this initial set cannot be slashed (see the [Consumer Initiated Slashing](#consumer-initiated-slashing) section).
+> This means that the consumer chain is *not yet secured* by the provider chain.
+> Thus, to reduce the attack surface during channel initialization, the consumer chain SHOULD enable user transactions only after the CCV channel is established (i.e., after receiving the first VSC).
+> As a consequence, a malicious initial validator set can only influence the initialization of the CCV channel.
+
+For a more detailed description of Channel Initialization, take a look at the [technical specification](./methods#initialization).
+
+### Validator Set Update
+
+[↑ Back to Outline](#outline)
+
+In the context of VSCs, the CCV module enables the following functionalities:
+
+- On the provider chain,
+ - **provide** VSCs to the consumer chains, for them to update their validator sets according to the validator set of the provider chain;
+ providing VSCs entails sending `VSCPacket`s to all consumer chains;
+ - **register** VSC maturity notifications from the consumer chain.
+- On every consumer chain,
+ - **apply** the VSCs provided by the provider chain to the validator set of the consumer chain;
+ - **notify** the provider chain that the provided VSCs have matured on this consumer chain;
+ notifying of VSCs maturity entails sending `VSCMaturedPacket`s to the provider chain.
+
+These functionalities are depicted in the following figure that shows an overview of the Validator Set Update operation of CCV.
+For a more detailed description of Validator Set Update, take a look at the [technical specification](./methods#validator-set-update).
+
+
+
+#### Completion of Unbonding Operations
+
+In the context of single-chain validation, the completion of any unbonding operation requires the `UnbondingPeriod` to elapse since the operations was initiated (i.e., the operation MUST reach maturity).
+In the context of CCV, the completion MUST require also the unbonding operation to reach maturity on **all** consumer chains (for the [Security Model](#security-model) to be preserved).
+Therefore, the provider Staking module needs to be aware of the VSC maturity notifications registered by the provider CCV module.
+
+The ***provider chain*** achieves this through the following approach:
+
+- The Staking module is notifying the CCV module when any unbonding operation is initiated.
+ As a result, the CCV module maps all the unbonding operations to the corresponding VSCs.
+- When the CCV module registers maturity notifications for a VSC from all consumer chains, it notifies the Staking module of the maturity of all unbonding operations mapped to this VSC.
+ This enables the Staking module to complete the unbonding operations only when they reach maturity on both the provider chain and on all the consumer chains.
+
+This approach is depicted in the following figure that shows an overview of the interface between the provider CCV module and the provider Staking module in the context of the Validator Set Update operation of CCV:
+
+- In `Block 1`, two unbonding operations are initiated (i.e., `undelegate-1` and `redelegate-1`) in the provider Staking module.
+ For each operation, the provider Staking module notifies the provider CCV module.
+ As a result, the provider CCV module maps these to operation to `vscId`, which is the ID of the following VSC (i.e., `VSC1`).
+ The provider CCV module provides `VSC1` to all consumer chains.
+- In `Block 2`, the same approach is used for `undelegate-2`.
+- In `Block j`, `UnbondingPeriod` has elapsed since `Block 1`.
+ In the meantime, the provider CCV module registered maturity notifications for `VSC1` from all consumer chains
+ and, consequently, notified the provider Staking module of the maturity of both `undelegate-1` and `redelegate-1`.
+ As a result, the provider Staking module completes both unbonding operations in `Block j`.
+- In `Block k`, `UnbondingPeriod` has elapsed since `Block 2`.
+ In the meantime, the provider CCV module has NOT yet registered maturity notifications for `VSC2` from all consumer chains.
+ As a result, the provider Staking module CANNOT complete `undelegate-2` in `Block k`.
+ The unbonding operation is completed later once the provider CCV module registered maturity notifications for `VSC2` from all consumer chains.
+
+
+
+### Consumer Initiated Slashing
+
+[↑ Back to Outline](#outline)
+
+For the [Security Model](#security-model) to be preserved, misbehaving validators MUST be slashed (and MAY be jailed, i.e., removed from the validator set).
+A prerequisite to slashing validators is to receive valid evidence of their misbehavior.
+Thus, when slashing a validator, we distinguish between three events and the heights when they occur:
+
+- `infractionHeight`, the height at which the misbehavior (or infraction) happened;
+- `evidenceHeight`, the height at which the evidence of misbehavior is received;
+- `slashingHeight`, the height at which the validator is slashed (and jailed).
+
+> **Note**: In the context of single-chain validation, usually `evidenceHeight = slashingHeight`.
+
+The [Security Model](#security-model) guarantees that any misbehaving validator can be slashed for at least the unbonding period,
+i.e., as long as that validator's tokens are not unbonded yet, they can be slashed.
+However, if the tokens start unbonding before `infractionHeight` (i.e., the tokens did not contribute to the voting power that committed the infraction) then the tokens MUST NOT be slashed.
+
+In the context of CCV, validators (with tokens bonded on the provider chain) MUST be slashed for infractions committed on the consumer chains at heights for which they have voting power.
+Thus, although the infractions are committed on the consumer chains and evidence of these infractions is submitted to the consumer chains, the slashing happens on the provider chain. As a result, the Consumer Initiated Slashing operation requires, for every consumer chain, a mapping from consumer chain block heights to provider chain block heights.
+
+The following figure shows the intuition behind such a mapping using the provided VSCs.
+The four unbonding operations (i.e., undelegations) occur on the provider chain and, as a consequence, the provider chain provides VSCs to the consumer chain, e.g., `undelegate-3` results in `VSC3` being provided.
+The four colors (i.e., red, blue, green, and yellow) indicate the mapping of consumer chain heights to provider chain heights.
+Note that on the provider chain there is only one block of a given color.
+Also, note that the three white blocks between the green and the yellow blocks on the provider chain have the same validator set.
+As a result, a validator misbehaving on the consumer chain, e.g., in either of the two green blocks, is slashed the same as if misbehaving on the provider chain, e.g., in the green block.
+This ensures that once unbonding operations are initiated, the corresponding unbonding tokens are not slashed for infractions committed in the subsequent blocks, e.g., the tokens unbonding due to `undelegate-3` are not slashed for infractions committed in or after the green blocks.
+
+
+
+The following figure shows describes how CCV creates the mapping from consumer chain heights to provider chain heights.
+For clarity, we use `Hp*` and `Hc*` to denote block heights on the provider chain and consumer chain, respectively.
+
+
+
+- For every block, the provider CCV module maps the ID of the VSC it provides to the consumer chains to the height of the subsequent block, i.e., `VSCtoH[VSC.id] = Hp + 1`, for a VSC provided at height `Hp`.
+ Intuitively, this means that the validator updates in a provided VSC will update the voting power at height `VSCtoH[VSC.id]`.
+- For every block, every consumer CCV module maps the height of the subsequent block to the ID of the latest received VSC, e.g., `HtoVSC[Hc2 + 1] = VSC1.id`.
+ Intuitively, this means that the voting power on the consumer chain during a block `Hc` was updated by the VSC with ID `HtoVSC[Hc]`.
+ > **Note**: It is possible for multiple VSCs to be received by the consumer chain within the same block. For more details, take a look at the [Validator sets, validator updates and VSCs](./system_model_and_properties#validator-sets-validator-updates-and-vscs) section.
+- By default, every consumer CCV module maps any block height to `0` (i.e., VSC IDs start from `1`).
+ Intuitively, this means that the voting power on the consumer chain at height `Hc` with `HtoVSC(Hc) = 0` was setup at genesis during Channel Initialization.
+- For every consumer chain, the provider CCV module sets `VSCtoH[0]` to the height when it establishes the CCV channel to this consumer chain.
+ Note that the validator set on the provider chain at height `VSCtoH[0]` matches the validator set at the height when the first VSC is provided to this consumer chain.
+ This means that this validator set on the provider chain matches the validator set on the consumer chain at all heights `Hc` with `HtoVSC[Hc] = 0`.
+
+The following figure shows an overview of the Consumer Initiated Slashing operation of CCV.
+
+
+
+- At (evidence) height `Hc2`, the consumer chain receives evidence that a validator `V` misbehaved at (infraction) height `Hc1`.
+ As a result, the consumer CCV module sends a `SlashPacket` to the provider chain:
+ It makes a request to slash `V`, but it replaces the infraction height `Hc1` with `HtoVSC[Hc1]`,
+ i.e., the ID of the VSC that updated the "misbehaving voting power" or `0` if such a VSC does not exist.
+- The provider CCV module receives at (slashing) height `Hp1` the `SlashPacket` with `vscId = HtoVSC[Hc1]`.
+ As a result, it requests the provider Slashing module to slash `V`, but it set the infraction height to `VSCtoH[vscId]`, i.e.,
+ - if `vscId != 0`, the height on the provider chain where the voting power was updated by the VSC with ID `vscId`;
+ - otherwise, the height at which the CCV channel to this consumer chain was established.
+ > **Note**: As a consequence of slashing (and potentially jailing) `V`, the Staking module updates accordingly `V`'s voting power. This update MUST be visible in the next VSC provided to the consumer chains.
+
+For a more detailed description of Consumer Initiated Slashing, take a look at the [technical specification](./methods#consumer-initiated-slashing).
+
+### Reward Distribution
+
+[↑ Back to Outline](#outline)
+
+In the context of single-chain validation, the *Distribution module*, i.e., a module of the ABCI application, handles the distribution of rewards (i.e., block production rewards and transaction fees) to every validator account based on their total voting power;
+these rewards are then further distributed to the delegators.
+For an example, take a look at the [Distribution module documentation](/sdk/v0.53/build/modules/distribution) of Cosmos SDK.
+
+At the beginning of every block, the rewards for the previous block are pooled into a distribution module account.
+The Reward Distribution operation of CCV enables every consumer chain to transfer a fraction of these rewards to the provider chain.
+The operation consists of two steps that are depicted in the following figure:
+
+
+
+- At the beginning of every block on the consumer chain, a fraction of the rewards are transferred to an account on the consumer CCV module.
+- At regular intervals (e.g., every `1000` blocks), the consumer CCV module sends the accumulated rewards to the distribution module account on the provider chain through an IBC token transfer packet (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)).
+ Note that the IBC transfer packet is sent over a separate unordered channel.
+ As a result, the reward distribution is not synchronized with the other CCV operations,
+ e.g., some validators may miss out on some rewards by unbonding before an IBC transfer packet is received,
+ while other validators may get some extra rewards by bonding before an IBC transfer packet is received.
+
+> **Note**: From the perspective of the distribution module account on the provider chain, the rewards coming from the consumer chains are indistinguishable from locally collected rewards and thus, are distributed to all the validators and their delegators.
+
+As a prerequisite of this approach, every consumer chain must open a token transfer channel to the provider chain and be made aware of the address of the distribution module account on the provider chain, both of which happen during channel initialization.
+
+- On receiving a `ChanOpenAck` message, the consumer CCV module initiates the opening handshake for the token transfer channel using the same client and connection as for the CCV channel.
+- On receiving a `ChanOpenTry` message, the provider CCV module adds the address of the distribution module account to the channel version as metadata (as defined in [ICS 4](../../core/ics-004-channel-and-packet-semantics/README#definitions)).
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/system_model_and_properties.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/system_model_and_properties.mdx
new file mode 100644
index 00000000..eeb7d471
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/system_model_and_properties.mdx
@@ -0,0 +1,429 @@
+---
+title: "CCV: System Model and Properties"
+---
+
+## Outline
+
+- [Assumptions](#assumptions)
+- [Desired Properties](#desired-properties)
+ - [System Properties](#system-properties)
+ - [CCV Channel](#ccv-channel)
+ - [Validator Sets, Validator Updates and VSCs](#validator-sets-validator-updates-and-vscs)
+ - [Staking Module Interface](#staking-module-interface)
+ - [Validator Set Update](#validator-set-update)
+ - [Consumer Initiated Slashing](#consumer-initiated-slashing)
+ - [Reward Distribution](#reward-distribution)
+- [Correctness Reasoning](#correctness-reasoning)
+
+## Assumptions
+
+[↑ Back to Outline](#outline)
+
+As part of a modular ABCI application, CCV interacts with both the consensus engine (via ABCI) and other application modules (e.g, the Staking module).
+As an IBC application, CCV interacts with external relayers (defined in [ICS 18](../../relayer/ics-018-relayer-algorithms)).
+In this section we specify what we assume about these other components.
+A more thorough discussion of the environment in which CCV operates is given in the section [Placing CCV within an ABCI Application](./technical_specification#placing-ccv-within-an-abci-application).
+
+> **Intuition**:
+>
+> CCV safety relies on the *Safe Blockchain* assumption,
+i.e., neither *Live Blockchain* and *Correct Relayer* are required for safety.
+Note though that CCV liveness relies on both *Live Blockchain* and *Correct Relayer* assumptions;
+furthermore, the *Correct Relayer* assumption relies on both *Safe Blockchain* and *Live Blockchain* assumptions.
+>
+> The *Validator Update Provision*, *Unbonding Safety*, *Slashing Warranty*, and *Distribution Warranty* assumptions define what is needed from the ABCI application of the provider chain.
+>
+> The *Evidence Provision* assumptions defines what is needed from the ABCI application of the consumer chains.
+
+- ***Safe Blockchain***: Both the provider and the consumer chains are *safe*. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol.
+- ***Live Blockchain***: Both the provider and the consumer chains are *live*. This means that, for every chain, the underlying consensus engine satisfies liveness (i.e., new blocks are eventually added to the chain).
+ > **Note**: Both *Safe Blockchain* and *Live Blockchain* assumptions require the consensus engine's assumptions to hold, e.g., less than a third of the voting power is Byzantine. For an example, take a look at the [Tendermint Paper](https://arxiv.org/pdf/1807.04938.pdf).
+
+- ***Correct Relayer***: There is at least one *correct*, *live* relayer between the provider and consumer chains. This assumption has the following implications.
+ - The opening handshake messages on the CCV channel are relayed before the Channel Initialization subprotocol times out (see `initTimeout`).
+ - Every packet sent on the CCV channel is relayed to the receiving end before the packet timeout elapses (see both `vscTimeout` and `ccvTimeoutTimestamp`).
+ - A correct relayer will eventually relay packets on the token transfer channel.
+
+ Clearly, the CCV protocol is responsible of setting the timeouts (see `ccvTimeoutTimestamp`, `vscTimeout`, `initTimeout` in the [CCV State](./data_structures#ccv-state)), such that the *Correct Relayer* assumption is feasible.
+ > **Discussion**: IBC relies on timeouts to signal that a sent packet is not going to be received on the other end.
+ > Once an ordered IBC channel timeouts, the channel is closed (see [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+ > The *Correct Relayer* assumption is necessary to ensure that the CCV channel **cannot** ever timeout and, as a result, cannot transit to the closed state.
+ >
+ > **In practice**, the *Correct Relayer* assumption is realistic since any validator could play the role of the relayer and it is in the best interest of correct validators to successfully relay packets.
+ >
+ > The following strategy is a practical example of how to ensure the *Correct Relayer* assumption holds.
+ > Let S denote the sending chain and D the destination chain;
+ > and let `drift(S,D)` be the time drift between S and D,
+ > i.e., `drift(S,D) = S.currentTimestamp() - D.currentTimestamp()` (`drift(S,D) > 0` means that S is "ahead" of D).
+ > For every packet, S only sets `timeoutTimestamp = S.currentTimestamp() + to`, with `to` an application-level parameter.
+ > The `timeoutTimestamp` indicates *a timestamp on the destination chain* after which the packet will no longer be processed (cf. [ICS 4](../../core/ics-004-channel-and-packet-semantics)).
+ > Therefore, the packet MUST be relayed within a time period of `to - drift(S,D)`,
+ > i.e., `to - drift(S,D) > RTmax`, where `RTmax` is the maximum relaying time across all packet.
+ > Theoretically, choosing the value of `to` requires knowing the value of `drift(S,D)` (i.e., `to > drift(S,D)`);
+ > yet, `drift(S,D)` is not known at a chain level.
+ > In practice, choosing `to` such that `to >> drift(S,D)` and `to >> RTmax`, e.g., `to = 4 weeks`, makes the *Correct Relayer* assumption feasible.
+
+- ***Validator Update Provision***: Let `{U1, U2, ..., Ui}` be a batch of validator updates applied (by the provider Staking module) to the validator set of the provider chain at block height `h`.
+ Then, the batch of validator updates obtained (by the provider CCV module) from the provider Staking module at height `h` MUST be exactly the batch `{U1, U2, ..., Ui}`.
+
+- ***Unbonding Safety***: Let `uo` be any unbonding operation that starts with an unbonding transaction being executed
+ and completes with the event that returns the corresponding stake;
+ let `U(uo)` be the validator update caused by initiating `uo`;
+ let `vsc(uo)` be the VSC that contains `U(uo)`.
+ Then,
+ - (*unbonding initiation*) the provider CCV module MUST be notified of `uo`'s initiation before receiving `U(uo)`;
+ - (*unbonding completion*) `uo` MUST NOT complete on the provider chain before the provider chain registers notifications of `vsc(uo)`'s maturity from all consumer chains.
+ > **Note**: Depending on the implementation, the (*unbonding initiation*) part of the *Unbonding Safety* MAY NOT be necessary for validator unbonding operations.
+
+- ***Slashing Warranty***: If the provider ABCI application (e.g., the Slashing module) receives a request to slash a validator `val` that misbehaved at block height `h`, then it slashes the amount of tokens `val` had bonded at height `h` except the amount that has already completely unbonded.
+
+- ***Evidence Provision***: If the consumer ABCI application receives a valid evidence of misbehavior at block height `h`, then it MUST submit it to the consumer CCV module *exactly once* and at the same height `h`.
+ Furthermore, the consumer ABCI application MUST NOT submit invalid evidence to the consumer CCV module.
+ > **Note**: What constitutes a valid evidence of misbehavior depends on the type of misbehavior and it is outside the scope of this specification.
+
+- ***Distribution Warranty***: The provider ABCI application (e.g., the Distribution module) distributes the tokens from the distribution module account among the validators that are part of the validator set.
+
+## Desired Properties
+
+The following properties are concerned with **one provider chain** providing security to **multiple consumer chains**.
+Between the provider chain and each consumer chain, a separate (unique) CCV channel is established.
+
+> **Note**: Except for liveness properties -- *Channel Liveness*, *Apply VSC Liveness*, *Register Maturity Liveness*, and *Distribution Liveness* -- none of the properties of CCV require the *Correct Relayer* assumption to hold.
+> Nonetheless, the *Correct Relayer* assumption is necessary to guarantee the systems properties (except for *Validator Set Replication*) -- *Bond-Based Consumer Voting Power*, *Slashable Consumer Misbehavior*, and *Consumer Rewards Distribution*.
+
+### System Properties
+
+[↑ Back to Outline](#outline)
+
+We use the following notations:
+
+- `ts(h)` is the timestamp of a block with height `h`, i.e., `ts(h) = B.currentTimestamp()`, where `B` is the block at height `h`;
+- `pBonded(h,val)` is the number of tokens bonded by validator `val` on the provider chain at block height `h`;
+- `pUnbonding(h,val)` is the number of tokens a validator `val` starts unbonding on the provider at block height `h`;
+- `VP(T)` is the voting power associated to a number `T` of tokens;
+- `Power(c,h,val)` is the voting power granted to a validator `val` on a chain `c` at block height `h`;
+- `Token(power)` is the amount of tokens necessary to be bonded (on the provider chain) by a validator to be granted `power` voting power,
+ i.e., `Token(VP(T)) = T`;
+- `slash(val, h, hi, sf)` is the amount of token slashed from a validator `val` on the provider chain (i.e., `pc`) at height `h` for an infraction (with a slashing fraction of `sf`) committed at (provider) height `hi`,
+ i.e., `slash(val, h, hi, sf) = sf * Token(Power(pc,hi,val))`;
+ note that the infraction can be committed also on a consumer chain, in which case `hi` is the corresponding height on the provider chain.
+
+Also, we use `ha << hb` to denote an order relation between heights, i.e., the block at height `ha` *happens before* the block at height `hb`.
+For heights on the same chain, `<<` is equivalent to `<`, i.e., `ha << hb` entails `hb` is larger than `ha`.
+For heights on two different chains, `<<` is establish by the packets sent over an order channel between two chains,
+i.e., if a chain `A` sends at height `ha` a packet to a chain `B` and `B` receives it at height `hb`, then `ha << hb`.
+> **Note**: `<<` is transitive, i.e., `ha << hb` and `hb << hc` entail `ha << hc`.
+>
+> **Note**: The block on the proposer chain that handles a governance proposal to spawn a new consumer chain `cc` *happens before* all the blocks of `cc`.
+
+CCV provides the following system properties.
+
+- ***Validator Set Replication***: Every validator set on any consumer chain MUST either be or have been a validator set on the provider chain.
+
+- ***Bond-Based Consumer Voting Power***: Let `val` be a validator, `cc` be a consumer chain, both `hc` and `hc'` be heights on `cc`, and both `hp` and `hp'` be heights on the provider chain, such that
+ - `val` has `Power(cc,hc,val)` voting power on `cc` at height `hc`;
+ - `hc'` is the smallest height on `cc` that satisfies `ts(hc') >= ts(hc) + UnbondingPeriod`, i.e., `val` cannot completely unbond on `cc` before `hc'`;
+ - `hp` is the largest height on the provider chain that satisfies `hp << hc`, i.e., `Power(pc,hp,val) = Power(cc,hc,val)`, where `pc` is the provider chain;
+ - `hp'` is the smallest height on the provider chain that satisfies `hc' << hp'`, i.e., `val` cannot completely unbond on the provider chain before `hp'`;
+ - `sumUnbonding(hp, h, val)` is the sum of all tokens of `val` that start unbonding on the provider at all heights `hu` and are still unbonding at height `h`, such that `hp < hu <= h`
+ - `sumSlash(hp, h, val)` is the sum of the slashes of `val` at all heights `hs` for infractions committed at `hp`, such that `hp < hs <= h`.
+
+ Then for all heights `h` on the provider chain,
+
+ ```typescript
+ hp <= h < hp':
+ Power(cc,hc,val) <= VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) )
+ ```
+
+ > **Note**: The reason for `+ sumUnbonding(hp, h, val)` in the above inequality is that tokens that `val` start unbonding after `hp` have contributed to the power granted to `val` at height `hc` on `cc` (i.e., `Power(cc,hc,val)`).
+ > As a result, these tokens should be available for slashing until `hp'`.
+ >
+ > **Note**: The reason for `+ sumSlash(hp, h, val)` in the above inequality is that slashing `val` reduces its locked tokens (i.e., `pBonded(h,val)` and `sumUnbonding(hp, h, val)`), however it does not reduce the power already granted to it at height `hc` on `cc` (i.e., `Power(cc,hc,val)`).
+ >
+ > **Intuition**: The *Bond-Based Consumer Voting Power* property ensures that validators that validate on the consumer chains have enough tokens bonded on the provider chain for a sufficient amount of time such that the security model holds.
+ > This means that if the validators misbehave on the consumer chains, their tokens bonded on the provider chain can be slashed during the unbonding period.
+ > For example, if one unit of voting power requires `1.000.000` bonded tokens (i.e., `VP(1.000.000)=1`),
+ > then a validator that gets one unit of voting power on a consumer chain must have at least `1.000.000` tokens bonded on the provider chain until the unbonding period elapses on the consumer chain.
+ >
+ > **Note**: When an existing chain becomes a consumer chain (see [Channel Initialization: Existing Chains](./overview_and_basic_concepts#channel-initialization-existing-chains)), the existing validator set is replaced by the provider validator set.
+ > For safety, the stake bonded by the existing validator set must remain bonded until the unbonding period elapses.
+ > Thus, the existing Staking module must be kept for at least the unbonding period.
+
+- ***Slashable Consumer Misbehavior***: If a validator `val` commits an infraction, with a slashing fraction of `sf`, on a consumer chain `cc` at a block height `hi`,
+ then any evidence of misbehavior that is received by `cc` at height `he`, such that `ts(he) < ts(hi) + UnbondingPeriod`,
+ MUST results in *exactly* the amount of tokens `sf*Token(Power(cc,hi,val))` to be slashed on the provider chain.
+ Furthermore, `val` MUST NOT be slashed more than once for the same misbehavior.
+ > **Note:** Unlike in single-chain validation, in CCV the tokens `sf*Token(Power(cc,hi,val))` MAY be slashed even if the evidence of misbehavior is received at height `he` such that `ts(he) >= ts(hi) + UnbondingPeriod`,
+ since unbonding operations need to reach maturity on both the provider and all the consumer chains.
+ >
+ > **Note:** The *Slashable Consumer Misbehavior* property also ensures that if a delegator starts unbonding an amount `x` of tokens from `val` before height `hi`, then `x` will not be slashed, since `x` is not part of `Token(Power(c,hi,val))`.
+
+- ***Consumer Rewards Distribution***: If a consumer chain sends to the provider chain an amount `T` of tokens as reward for providing security, then
+ - `T` (equivalent) tokens MUST be eventually minted on the provider chain and then distributed among the validators that are part of the validator set;
+ - the total supply of tokens MUST be preserved, i.e., the `T` (original) tokens are escrowed on the consumer chain.
+
+### CCV Channel
+
+[↑ Back to Outline](#outline)
+
+- ***Channel Uniqueness***: The channel between the provider chain and a consumer chain MUST be unique.
+- ***Channel Validity***: If a packet `P` is received by one end of a CCV channel, then `P` MUST have been sent by the other end of the channel.
+- ***Channel Order***: If a packet `P1` is sent over a CCV channel before a packet `P2`, then `P2` MUST NOT be received by the other end of the channel before `P1`.
+- ***Channel Liveness***: Every packet sent over a CCV channel MUST eventually be received by the other end of the channel.
+
+### Validator Sets, Validator Updates and VSCs
+
+[↑ Back to Outline](#outline)
+
+In this section, we provide a short discussion on how the validator set, the validator updates, and the VSCs relates in the context of multiple chains.
+
+Every chain consists of a sequence of blocks.
+At the end of each block, validator updates (i.e., changes in the validators voting power) results in changes in the validator set of the next block.
+Thus, the sequence of blocks produces a sequence of validator updates and a sequence of validator sets.
+Furthermore, the sequence of validator updates on the provider chain results in a sequence of VSCs to all consumer chains.
+Ideally, this sequence of VSCs is applied by every consumer chain, resulting in a sequence of validator sets identical to the one on the provider chain.
+However, in general this need not be the case. The reason is twofold:
+
+- first, given any two chains `A` and `B`, we cannot assume that `A`'s rate of adding new block is the same as `B`'s rate
+ (i.e., we consider the sequences of blocks of any two chains to be completely asynchronous);
+- and second, due to relaying delays, we cannot assume that the rate of sending VSCs matches the rate of receiving VSCs.
+
+As a result, it is possible for multiple VSCs to be received by a consumer chain within the same block and be applied together at the end of the block,
+i.e., the validator updates within the VSCs are being *aggregated* by keeping only the latest update per validator.
+As a consequence, some validator sets on the provider chain are not existing on all consumer chains.
+In other words, the validator sets on each consumer chain form a *subsequence* of the validator sets on the provider chain.
+Nonetheless, as a **requirement of CCV**, *all the validator updates on the provider chain MUST be included in the sequence of validator sets on all consumer chains*.
+
+This is possible since every validator update contains *the absolute voting power* of that validator.
+Given a validator `val`, the sequence of validator updates targeting `val` (i.e., updates of the voting power of `val`) is the prefix sum of the sequence of relative changes of the voting power of `val`.
+Thus, given a validator update `U` targeting `val` that occurs at a block height `h`,
+`U` *sums up* all the relative changes of the voting power of `val` that occur until height `h`,
+i.e., `U = c_1+c_2+...+c_i`, such that `c_i` is the last relative change that occurs by `h`.
+Note that relative changes are integer values.
+
+As a consequence, CCV can rely on the following property:
+
+- ***Validator Update Inclusion***: Let `U1` and `U2` be two validator updates targeting the same validator `val`.
+ If `U1` occurs before `U2`, then `U2` sums up all the changes of the voting power of `val` that are summed up by `U1`, i.e.,
+ - `U1 = c_1+c_2+...+c_i` and
+ - `U2 = c_1+c_2+...+c_i+c_(i+1)+...+c_j`.
+
+The *Validator Update Inclusion* property enables CCV to aggregate multiple VSCs.
+It is sufficient for the consumer chains to apply only the last update per validator.
+Since the last update of a validator *includes* all the previous updates of that validator, once it is applied, all the previous updates are also applied.
+
+### Staking Module Interface
+
+[↑ Back to Outline](#outline)
+
+The following properties define the guarantees of CCV on *providing* VSCs to the consumer chains as a consequence of validator updates on the provider chain.
+
+- ***Validator Update To VSC Validity***: Every VSC provided to a consumer chain MUST contain only validator updates that were applied to the validator set of the provider chain (i.e., resulted from a change in the amount of bonded tokens on the provider chain).
+- ***Validator Update To VSC Order***: Let `U1` and `U2` be two validator updates on the provider chain. If `U1` occurs before `U2`, then `U2` MUST NOT be included in a provided VSC before `U1`. Note that the order within a single VSC is not relevant.
+- ***Validator Update To VSC Liveness***: Every update of a validator in the validator set of the provider chain MUST eventually be included in a VSC provided to all consumer chains.
+
+Note that as a consequence of the *Validator Update To VSC Liveness* property, CCV guarantees the following property:
+
+- **Provide VSC uniformity**: If the provider chain provides a VSC to a consumer chain, then it MUST eventually provide that VSC to all consumer chains.
+
+### Validator Set Update
+
+[↑ Back to Outline](#outline)
+
+The provider chain providing VSCs to the consumer chains has two desired outcomes: the consumer chains apply the VSCs; and the provider chain registers VSC maturity notifications from every consumer chain.
+Thus, for clarity, we split the properties of VSCs in two: properties of applying provided VSCs on the consumer chains; and properties of registering VSC maturity notifications on the provider chain.
+For simplicity, we focus on a single consumer chain.
+
+The following properties define the guarantees of CCV on *applying* on the consumer chain VSCs *provided* by the provider chain.
+
+- ***Apply VSC Validity***: Every VSC applied by the consumer chain MUST be provided by the provider chain.
+- ***Apply VSC Order***: If a VSC `vsc1` is provided by the provider chain before a VSC `vsc2`, then the consumer chain MUST NOT apply the validator updates included in `vsc2` before the validator updates included in `vsc1`.
+- ***Apply VSC Liveness***: If the provider chain provides a VSC `vsc`, then the consumer chain MUST eventually apply all validator updates included in `vsc`.
+
+The following properties define the guarantees of CCV on *registering* on the provider chain maturity notifications (from the consumer chain) of VSCs *provided* by the provider chain to the consumer chain.
+
+- ***Register Maturity Validity***: If the provider chain registers a maturity notification of a VSC from the consumer chain, then the provider chain MUST have provided that VSC to the consumer chain.
+- ***Register Maturity Timeliness***: The provider chain MUST NOT register a maturity notification of a VSC `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`.
+- ***Register Maturity Order***: If a VSC `vsc1` was provided by the provider chain before another VSC `vsc2`, then the provider chain MUST NOT register the maturity notification of `vsc2` before the maturity notification of `vsc1`.
+- ***Register Maturity Liveness***: If the provider chain provides a VSC `vsc` to the consumer chain, then the provider chain MUST eventually register a maturity notification of `vsc` from the consumer chain.
+
+### Consumer Initiated Slashing
+
+[↑ Back to Outline](#outline)
+
+- ***Consumer Slashing Warranty***: Let `cc` be a consumer chain, such that its CCV module receives at height `he` evidence that a validator `val` misbehaved on `cc` at height `hi`.
+ Let `hv` be the height when the CCV module of `cc` receives the first VSC from the provider CCV module, i.e., the height when the CCV channel is established.
+ Then, the CCV module of `cc` MUST send (to the provider CCV module) *exactly one* `SlashPacket` `P`, such that
+ - `P` is sent at height `h = max(he, hv)`;
+ - `P.val = val` and `P.id = HtoVSC[hi]`,
+ i.e., the ID of the latest VSC that updated the validator set on `cc` at height `hi` or `0` if such a VSC does not exist (if `hi < hv`).
+ > **Note**: A consequence of the *Consumer Slashing Warranty* property is that the initial validator set on a consumer chain cannot be slashed during the initialization of the CCV channel.
+ Therefore, consumer chains *SHOULD NOT allow user transactions before the CCV channel is established*.
+ Note that once the CCV channel is established (i.e., a VSC is received from the provider CCV module), CCV enables the slashing of the initial validator set for infractions committed during channel initialization.
+
+- ***Provider Slashing Warranty***: If the provider CCV module receives from a consumer chain `cc` a `SlashPacket` containing a validator `val` and a VSC ID `vscId`,
+ then it MUST make *exactly one* request to the provider Slashing module to slash `val` for misbehaving at height `h`, such that
+ - if `vscId = 0`, `h` is the height of the block when the provider chain established a CCV channel to `cc`;
+ - otherwise, `h` is the height of the block immediately subsequent to the block when the provider chain provided to `cc` the VSC with ID `vscId`.
+
+ Furthermore, the provider CCV module MUST make this slash request before registering any maturity notifications received from `cc` after the `SlashPacket`.
+
+- ***VSC Maturity and Slashing Order***: If a consumer chain sends to the provider chain a `SlashPacket` before a maturity notification of a VSC, then the provider chain MUST NOT receive the maturity notification before the `SlashPacket`.
+ > **Note**: *VSC Maturity and Slashing Order* requires the VSC maturity notifications to be sent through their own IBC packets (i.e., `VSCMaturedPacket`s) instead of e.g., through acknowledgements of `VSCPacket`s.
+
+### Reward Distribution
+
+[↑ Back to Outline](#outline)
+
+- ***Distribution Liveness***: If the CCV module on a consumer chain sends to the distribution module account on the provider chain an amount `T` of tokens as reward for providing security, then `T` (equivalent) tokens are eventually minted in the distribution module account on the provider chain.
+
+## Correctness Reasoning
+
+[↑ Back to Outline](#outline)
+
+In this section we argue the correctness of the CCV protocol described in the [Technical Specification](./technical_specification),
+i.e., we informally prove the properties described in the [previous section](#desired-properties).
+
+- ***Channel Uniqueness***: The consumer chain side of the CCV channel is established when the consumer CCV module receives the *first* `ChanOpenAck` message that is successfully executed; all subsequent `ChanOpenAck` messages will fail (cf. *Safe Blockchain*).
+ Let `ccvChannel` denote this channel. Then, `ccvChannel` is the only `OPEN` channel that can be connected to a port owned by the consumer CCV module.
+ The provider chain side of the CCV channel is established when the provider CCV module receives the *first* `ChanOpenConfirm` message that is successfully executed; all subsequent `ChanOpenConfirm` messages will fail (cf. *Safe Blockchain*).
+ The `ccvChannel` is the only channel for which `ChanOpenConfirm` can be successfully executed (cf. *Safe Blockchain*, i.e., IBC channel opening handshake guarantee).
+ As a result, `ccvChannel` is unique.
+ Moreover, ts existence is guaranteed by the *Correct Relayer* assumption.
+
+- ***Channel Validity***: Follows directly from the *Safe Blockchain* assumption.
+
+- ***Channel Order***: The provider chain accepts only ordered channels when receiving a `ChanOpenTry` message (cf. *Safe Blockchain*).
+ Similarly, the consumer chain accepts only ordered channels when receiving `ChanOpenInit` messages (cf. *Safe Blockchain*).
+ Thus, the property follows directly from the fact that the CCV channel is ordered.
+
+- ***Channel Liveness***: The property follows from the *Correct Relayer* assumption.
+
+- ***Validator Update To VSC Validity***: The provider CCV module provides only VSCs that contain validator updates obtained from the Staking module,
+ i.e., by calling the `GetValidatorUpdates()` method (cf. *Safe Blockchain*).
+ Furthermore, these validator updates were applied to the validator set of the provider chain (cf. *Validator Update Provision*).
+
+- ***Validator Update To VSC Order***: We prove the property through contradiction.
+ Given two validator updates `U1` and `U2`, with `U1` occurring on the provider chain before `U2`, we assume `U2` is included in a provided VSC before `U1`.
+ However, `U2` could not have been obtained by the provider CCV module before `U1` (cf. *Validator Update Provision*).
+ Thus, the provider CCV module could not have provided a VSC that contains `U2` before a VSC that contains `U1` (cf. *Safe Blockchain*), which contradicts the initial assumption.
+
+- ***Validator Update To VSC Liveness***: The provider CCV module eventually provides to all consumer chains VSCs containing all validator updates obtained from the provider Staking module (cf. *Safe Blockchain*, *Life Blockchain*).
+ Thus, it is sufficient to prove that every update of a validator in the validator set of the provider chain MUST eventually be obtained from the provider Staking module.
+ We prove this through contradiction. Given a validator update `U` that is applied to the validator set of the provider chain at the end of a block `B` with height `h`, we assume `U` is never obtained by the provider CCV module.
+ However, at height `h`, the provider CCV module tries to obtain a new batch of validator updates from the provider Staking module (cf. *Safe Blockchain*).
+ Thus, this batch of validator updates MUST contain all validator updates applied to the validator set of the provider chain at the end of block `B`, including `U` (cf. *Validator Update Provision*), which contradicts the initial assumption.
+
+- ***Apply VSC Validity***: The property follows from the following two assertions.
+ - The consumer chain only applies VSCs received in `VSCPacket`s through the CCV channel (cf. *Safe Blockchain*).
+ - The provider chain only sends `VSCPacket`s containing provided VSCs (cf. *Safe Blockchain*).
+
+- ***Apply VSC Order***: We prove the property through contradiction.
+ Given two VSCs `vsc1` and `vsc2` such that the provider chain provides `vsc1` before `vsc2`, we assume the consumer chain applies the validator updates included in `vsc2` before the validator updates included in `vsc1`.
+ The following sequence of assertions leads to a contradiction.
+ - The provider chain could not have sent a `VSCPacket` `P2` containing `vsc2` before a `VSCPacket` `P1` containing `vsc1` (cf. *Safe Blockchain*).
+ - The consumer chain could not have received `P2` before `P1` (cf. *Channel Order*).
+ - Given the *Safe Blockchain* assumption, we distinguish two cases.
+ - First, the consumer chain receives `P1` during block `B1` and `P2` during block `B2` (with `B1` < `B2`).
+ Then, it applies the validator updates included in `vsc1` at the end of `B1` and the validator updates included in `vsc2` at the end of `B2` (cf. *Validator Update Inclusion*), which contradicts the initial assumption.
+ - Second, the consumer chain receives both `P1` and `P2` during the same block.
+ Then, it applies the validator updates included in both `vsc1` and `vsc2` at the end of the block.
+ Thus, it could not have apply the validator updates included in `vsc2` before.
+
+- ***Apply VSC Liveness***: The provider chain eventually sends over the CCV channel a `VSCPacket` containing `vsc` (cf. *Safe Blockchain*, *Life Blockchain*).
+ As a result, the consumer chain eventually receives this packet (cf. *Channel Liveness*).
+ Then, the consumer chain aggregates all received VSCs at the end of the block and applies all the aggregated updates (cf. *Safe Blockchain*, *Life Blockchain*).
+ As a result, the consumer chain applies all validator updates in `vsc` (cf. *Validator Update Inclusion*).
+
+- ***Register Maturity Validity***: The property follows from the following sequence of assertions.
+ - The provider chain only registers VSC maturity notifications when receiving on the CCV channel a `VSCMaturedPacket`s notifying the maturity of those VSCs (cf. *Safe Blockchain*).
+ - The provider chain receives on the CCV channel only packets sent by the consumer chain (cf. *Channel Validity*).
+ - The consumer chain only sends `VSCMaturedPacket`s matching the `VSCPacket`s it receives on the CCV channel (cf. *Safe Blockchain*).
+ - The consumer chain receives on the CCV channel only packets sent by the provider chain (cf. *Channel Validity*).
+ - The provider chain only sends `VSCPacket`s containing provided VSCs (cf. *Safe Blockchain*).
+
+- ***Register Maturity Timeliness***: We prove the property through contradiction.
+ Given a VSC `vsc` provided by the provider chain to the consumer chain, we assume that the provider chain registers a maturity notification of `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`.
+ The following sequence of assertions leads to a contradiction.
+ - The provider chain could not have register a maturity notification of `vsc` before receiving on the CCV channel a `VSCMaturedPacket` `P` with `P.id = vsc.id` (cf. *Safe Blockchain*).
+ - The provider chain could not have received `P` on the CCV channel before the consumer chain sent it (cf. *Channel Validity*).
+ - The consumer chain could not have sent `P` before at least `UnbondingPeriod` has elapsed since receiving a `VSCPacket` `P'` with `P'.id = P.id` on the CCV channel (cf. *Safe Blockchain*).
+ Note that since time is measured in terms of the block time, the time of receiving `P'` is the same as the time of applying `vsc`.
+ - The consumer chain could not have received `P'` on the CCV channel before the provider chain sent it (cf. *Channel Validity*).
+ - The provider chain could not have sent `P'` before providing `vsc`.
+ - Since the duration of sending packets through the CCV channel cannot be negative, the provider chain could not have registered a maturity notification of `vsc` before `UnbondingPeriod` has elapsed on the consumer chain since the consumer chain applied `vsc`.
+
+- ***Register Maturity Order***: We prove the property through contradiction. Given two VSCs `vsc1` and `vsc2` such that the provider chain provides `vsc1` before `vsc2`, we assume the provider chain registers the maturity notification of `vsc2` before the maturity notification of `vsc1`.
+ The following sequence of assertions leads to a contradiction.
+ - The provider chain could not have sent a `VSCPacket` `P2`, with `P2.updates = C2`, before a `VSCPacket` `P1`, with `P1.updates = C1` (cf. *Safe Blockchain*).
+ - The consumer chain could not have received `P2` before `P1` (cf. *Channel Order*).
+ - The consumer chain could not have sent a `VSCMaturedPacket` `P2'`, with `P2'.id = P2.id`, before a `VSCMaturedPacket` `P1'`, with `P1'.id = P1.id` (cf. *Safe Blockchain*).
+ - The provider chain could not have received `P2'` before `P1'` (cf. *Channel Order*).
+ - The provider chain could not have registered the maturity notification of `vsc2` before the maturity notification of `vsc1` (cf. *Safe Blockchain*).
+
+- ***Register Maturity Liveness***: The property follows from the following sequence of assertions.
+ - The provider chain eventually sends on the CCV channel a `VSCPacket` `P`, with `P.updates = C` (cf. *Safe Blockchain*, *Life Blockchain*).
+ - The consumer chain eventually receives `P` on the CCV channel (cf. *Channel Liveness*).
+ - The consumer chain eventually sends on the CCV channel a `VSCMaturedPacket` `P'`, with `P'.id = P.id` (cf. *Safe Blockchain*, *Life Blockchain*).
+ - The provider chain eventually receives `P'` on the CCV channel (cf. *Channel Liveness*).
+ - The provider chain eventually registers the maturity notification of `vsc` (cf. *Safe Blockchain*, *Life Blockchain*).
+
+- ***Consumer Slashing Warranty***: Follows directly from *Safe Blockchain*.
+
+- ***Provider Slashing Warranty***: Follows directly from *Safe Blockchain*.
+
+- ***VSC Maturity and Slashing Order***: Follows directly from *Channel Order*.
+
+- ***Distribution Liveness***: The CCV module on the consumer chain sends to the provider chain an amount `T` of tokens through an IBC token transfer packet (as defined in [ICS 20](../ics-020-fungible-token-transfer/README)).
+ Thus, if the packet is relayed within the timeout period, then `T` (equivalent) tokens are minted on the provider chain.
+ Otherwise, the `T` tokens are refunded to the consumer CCV module account.
+ In this case, the `T` tokens will be part of the next token transfer packet.
+ Eventually, a correct relayer will relay a token transfer packet containing the `T` tokens (cf. *Correct Relayer*, *Life Blockchain*).
+ As a result, `T` (equivalent) tokens are eventually minted on the provider chain.
+
+- ***Validator Set Replication***: The property follows from the *Safe Blockchain* assumption and both the *Apply VSC Validity* and *Validator Update To VSC Validity* properties.
+
+- ***Bond-Based Consumer Voting Power***: The existence of `hp` is given by construction, i.e., the block on the proposer chain that handles a governance proposal to spawn a new consumer chain `cc` *happens before* all the blocks of `cc`.
+ The existence of `hc'` and `hp'` is given by *Life Blockchain* and *Channel Liveness*.
+
+ To prove the *Bond-Based Consumer Voting Power* property, we use the following property that follows directly from the design of the protocol (cf. *Safe Blockchain*, *Life Blockchain*).
+ - *Property1*: Let `val` be a validator; let `Ua` and `Ub` be two updates of `val` that are applied subsequently by a consumer chain `cc`, at block heights `ha` and `hb`, respectively (i.e., no other updates of `val` are applied in between).
+ Then, `Power(cc,ha,val) = Power(cc,h,val)`, for all block heights `h`, such that `ha <= h < hb` (i.e., the voting power granted to `val` on `cc` in the period between `ts(ha)` and `ts(hb)` is constant).
+
+ We prove the *Bond-Based Consumer Voting Power* property through contradiction.
+ We assume there exist a height `h` on the provider chain between `hp` and `hp'` such that `Power(cc,hc,val) > VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) )`.
+ The following sequence of assertions leads to a contradiction.
+ - Let `U1` be the latest update of `val` that is applied by `cc` before or not later than block height `hc`
+ (i.e., `U1` is the update that sets `Power(cc,hc,val)` for `val`).
+ Let `hp1` be the height at which `U1` occurs on the provider chain; let `hc1` be the height at which `U1` is applied on `cc`.
+ Then, `hp1 << hc1 <= hc`, `hp1 <= hp`, and `Power(cc,hc,val) = Power(cc,hc1,val) = VP(pBonded(hp1,val))`.
+ This means that some of the tokens bonded by `val` at height `hp1` were *completely* unbonded before or not later than height `hp'` (cf. `Power(cc,hc,val) > VP( pBonded(h,val) + sumUnbonding(hp, h, val) + sumSlash(hp, h, val) )`).
+ - Let `uo` be the first such unbonding operation that is initiated on the provider chain at height `hp2`, such that `hp1 < hp2 <= hp'`.
+ Note that at height `hp2`, the tokens unbonded by `uo` are part of `pUnbonding(hp2,val)`.
+ Let `U2` be the validator update caused by initiating `uo`.
+ Let `hc2` be the height at which `U2` is applied on `cc`; clearly, `Power(cc,hc2,val) < Power(cc,hc,val)`.
+ Note that the existence of `hc2` is ensured by *Validator Update To VSC Liveness* and *Apply VSC Liveness*.
+ Then, `hc2 > hc1` (cf. `hp2 > hp1`, *Validator Update To VSC Order*, *Apply VSC Order*).
+ - `Power(cc,hc,val) = Power(cc,hc1,val) = Power(cc,h,val)`, for all heights `h`, such that `hc1 <= h < hc2` (cf. *Property1*).
+ Thus, `hc2 > hc` (cf. `Power(cc,hc2,val) < Power(cc,hc,val)`).
+ - `uo` cannot complete before `ts(hc2) + UnbondingPeriod`, which means it cannot complete before `hc'` and thus it cannot complete before `hp'` (cf. `hc' << hp'`).
+
+- ***Slashable Consumer Misbehavior***: The second part of the *Slashable Consumer Misbehavior* property (i.e., `val` is not slashed more than once for the same misbehavior) follows directly from *Evidence Provision*, *Channel Validity*, *Consumer Slashing Warranty*, *Provider Slashing Warranty*.
+ To prove the first part of the *Slashable Consumer Misbehavior* property (i.e., exactly the amount of tokens `sf*Token(Power(cc,hi,val))` are slashed on the provider chain), we consider the following sequence of statements.
+ - The CCV module of `cc` receives at height `he` the evidence that `val` misbehaved on `cc` at height `hi` (cf. *Evidence Provision*, *Safe Blockchain*, *Life Blockchain*).
+ - Let `hv` be the height when the CCV module of `cc` receives the first VSC from the provider CCV module.
+ Then, the CCV module of `cc` sends at height `h = max(he, hv)` to the provider chain a `SlashPacket` `P`, such that `P.val = val` and `P.id = HtoVSC[hi]` (cf. *Consumer Slashing Warranty*).
+ - The provider CCV module eventually receives `P` (cf. *Channel Liveness*).
+ - The provider CCV module requests the provider Slashing module to slash `val` for misbehaving at height `hp = VSCtoH[P.id]` before handling any further maturity notifications received from the CCV module of `cc` (cf. *Provider Slashing Warranty*).
+ - The provider Slashing module slashes the amount of tokens `val` had bonded at height `hp` except the amount that has already completely unbonded (cf. *Slashing Warranty*).
+
+ Thus, it remains to be proven that `Token(Power(cc,hi,val)) = pBonded(hp,val)`, with `hp = VSCtoH[HtoVSC[hi]]`. We distinguish two cases:
+ - `HtoVSC[hi] != 0`, which means that by definition `HtoVSC[hi]` is the ID of the last VSC that update `Power(cc,hi,val)`.
+ Also by definition, this VSC contains the last updates to the voting power at height `VSCtoH[HtoVSC[hi]]` on the provider.
+ Thus, `Token(Power(cc,hi,val)) = pBonded(hp,val)`.
+ - `HtoVSC[hi] == 0`, which means that by definition `Power(cc,hi,val)` was setup at genesis during Channel Initialization.
+ Also by definition, this is the same voting power of the provider chain block when the first VSC was provided to that consumer chain, i.e., `VSCtoH[HtoVSC[hi]]`.
+ Thus, `Token(Power(cc,hi,val)) = pBonded(hp,val)`.
+
+- ***Consumer Rewards Distribution***: The first part of the *Consumer Rewards Distribution* property (i.e., the tokens are eventually minted on the provider chain and then distributed among the validators) follows directly from *Distribution Liveness* and *Distribution Warranty*.
+ The second part of the *Consumer Rewards Distribution* property (i.e., the total supply of tokens is preserved) follows directly from the *Supply* property of the Fungible Token Transfer protocol (see [ICS 20](../ics-020-fungible-token-transfer/README)).
diff --git a/ibc/next/spec/app/ics-028-cross-chain-validation/technical_specification.mdx b/ibc/next/spec/app/ics-028-cross-chain-validation/technical_specification.mdx
new file mode 100644
index 00000000..c0a1693e
--- /dev/null
+++ b/ibc/next/spec/app/ics-028-cross-chain-validation/technical_specification.mdx
@@ -0,0 +1,137 @@
+---
+title: "CCV: Technical Specification"
+---
+
+## Outline
+
+- [Placing CCV within an ABCI Application](#placing-ccv-within-an-abci-application)
+ - [Implemented Interfaces](#implemented-interfaces)
+ - [Interfacing Other Modules](#interfacing-other-modules)
+- [Data Structures and Methods](#data-structures-and-methods)
+
+## Placing CCV within an ABCI Application
+
+[↑ Back to Outline](#outline)
+
+Before describing the data structures and sub-protocols of the CCV protocol, we provide a short overview of the interfaces the CCV module implements and the interactions with the other ABCI application modules.
+
+### Implemented Interfaces
+
+- CCV is an **ABCI application module**, which means it MUST implement the logic to handle some of the messages received from the consensus engine via ABCI,
+ e.g., `InitChain`, `BeginBlock`, `EndBlock` (for more details, take a look at the [ABCI specification](https://github.com/tendermint/spec/tree/v0.7.1/spec/abci)).
+ In this specification we define the following methods that handle messages that are of particular interest to the CCV protocol:
+ - `InitGenesis()` -- Called when the chain is first started, on receiving an `InitChain` message from the consensus engine.
+ This is also where the application can inform the underlying consensus engine of the initial validator set.
+ - `BeginBlock()` -- Contains logic that is automatically triggered at the beginning of each block.
+ - `EndBlock()` -- Contains logic that is automatically triggered at the end of each block.
+ This is also where the application can inform the underlying consensus engine of changes in the validator set.
+
+- CCV is an **IBC module**, which means it MUST implement the module callbacks interface defined in [ICS 26](../../core/ics-026-routing-module/README#module-callback-interface). The interface consists of a set of callbacks for
+ - channel opening handshake, which we describe in the [Initialization](./methods#initialization) section;
+ - channel closing handshake, which we describe in the [Consumer Chain Removal](./methods#consumer-chain-removal) section;
+ - and packet relay, which we describe in the [Packet Relay](./methods#packet-relay) section.
+
+### Interfacing Other Modules
+
+- As an ABCI application module, the CCV module interacts with the underlying consensus engine through ABCI:
+ - On the provider chain,
+ - it initializes the application (e.g., binds to the expected IBC port) in the `InitGenesis()` method.
+ - On the consumer chain,
+ - it initializes the application (e.g., binds to the expected IBC port, creates a client of the provider chain) in the `InitGenesis()` method;
+ - it provides the validator updates in the `EndBlock()` method.
+
+- As an IBC module, the CCV module interacts with Core IBC for functionalities regarding
+ - port allocation ([ICS 5](../../core/ics-005-port-allocation)) via `portKeeper`;
+ - channels and packet semantics ([ICS 4](../../core/ics-004-channel-and-packet-semantics)) via `channelKeeper`;
+ - connection semantics ([ICS 3](../../core/ics-003-connection-semantics)) via `connectionKeeper`;
+ - client semantics ([ICS 2](../../core/ics-002-client-semantics)) via `clientKeeper`.
+
+- The consumer CCV module interacts with the IBC Token Transfer module ([ICS 20](../ics-020-fungible-token-transfer/README)) via `transferKeeper`.
+
+- For the [Initialization sub-protocol](./methods#initialization), the provider CCV module interacts with a Governance module by handling governance proposals to add new consumer chains.
+ If such proposals pass, then all validators on the provider chain MUST validate the consumer chain at spawn time;
+ otherwise they get slashed.
+ For an example of how governance proposals work, take a look at the [Governance module documentation](/sdk/v0.53/build/modules/gov) of Cosmos SDK.
+
+- The consumer pre-CCV module (i.e., the CCV module with `preCCV == true`) interacts with a Staking module on the consumer chain.
+ Note that once `preCCV` is set to `false`, the Staking module MUST no longer provide validator updates to the underlying consensus engine.
+ For an example of how staking works, take a look at the [Staking module documentation](/sdk/v0.53/build/modules/staking) of Cosmos SDK.
+ The interaction is defined by the following interface:
+
+ ```typescript
+ interface StakingKeeper {
+ // replace the validator set with valset
+ ReplaceValset(valset: [ValidatorUpdate])
+ }
+ ```
+
+- The provider CCV module interacts with a Staking module on the provider chain.
+ For an example of how staking works, take a look at the [Staking module documentation](/sdk/v0.53/build/modules/staking) of Cosmos SDK.
+ The interaction is defined by the following interface:
+
+ ```typescript
+ interface StakingKeeper {
+ // get UnbondingPeriod from the provider Staking module
+ UnbondingTime(): Duration
+
+ // get validator updates from the provider Staking module
+ GetValidatorUpdates(): [ValidatorUpdate]
+
+ // request the Staking module to put on hold
+ // the completion of an unbonding operation
+ PutUnbondingOnHold(id: uint64)
+
+ // notify the Staking module of an unboding operation that
+ // has matured from the perspective of the consumer chains
+ UnbondingCanComplete(id: uint64)
+ }
+ ```
+
+- The provider CCV module interacts with a Slashing module on the provider chain.
+ For an example of how slashing works, take a look at the [Slashing module documentation](/sdk/v0.53/build/modules/slashing) of Cosmos SDK.
+ The interaction is defined by the following interface:
+
+ ```typescript
+ interface SlashingKeeper {
+ // query the Slashing module for the slashing factor,
+ // which may be different for downtime infractions
+ GetSlashFactor(downtime: Bool): int64
+
+ // request the Slashing module to slash a validator
+ Slash(valAddress: string,
+ infractionHeight: int64,
+ power: int64,
+ slashFactor: int64)
+
+ // query the Slashing module for the jailing time,
+ // which may be different for downtime infractions
+ GetJailTime(downtime: Bool): int64
+
+ // request the Slashing module to jail a validator until time
+ JailUntil(valAddress: string, time: uint64)
+ }
+ ```
+
+- The following hook enables the provider CCV module to register operations to be execute when certain events occur within the provider Staking module:
+
+ ```typescript
+ // invoked by the Staking module after
+ // initiating an unbonding operation
+ function AfterUnbondingInitiated(opId: uint64);
+ ```
+
+- The consumer CCV module defines the following hooks that enable other modules to register operations to execute when certain events have occurred within CCV:
+
+ ```typescript
+ // invoked after a new validator is added to the validator set
+ function AfterCCValidatorBonded(valAddress: string);
+
+ // invoked after a validator is removed from the validator set
+ function AfterCCValidatorBeginUnbonding(valAddress: string);
+ ```
+
+## Data Structures and Methods
+
+[↑ Back to Outline](#outline)
+
+The remainder of this technical specification is split into [Data Structures](./data_structures) and [Methods](./methods).
diff --git a/ibc/next/spec/app/ics-029-fee-payment/README.mdx b/ibc/next/spec/app/ics-029-fee-payment/README.mdx
new file mode 100644
index 00000000..d5044ac3
--- /dev/null
+++ b/ibc/next/spec/app/ics-029-fee-payment/README.mdx
@@ -0,0 +1,605 @@
+---
+ics: 29
+title: General Fee Payment
+stage: draft
+category: IBC/APP
+requires: [4, 25, 26, 30]
+kind: instantiation
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchain.berlin
+ - name: Ethan Frey
+ email: ethan@confio.tech
+created: 2021-06-01
+modified: 2022-07-06
+---
+
+
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for handling fee
+payments on top of any ICS application protocol. It requires some changes to the acknowledgement, but can be adopted by any
+application, without forcing other applications to use this implementation.
+
+### Motivation
+
+There has been much discussion on a general incentivization mechanism for relayers. A simple proposal was created to
+[extend ICS-20 to incentivize relaying](https://github.com/cosmos/ibc/pull/577) on the destination chain. However,
+it was very specific to ICS-20 and would not work for other protocols. This was then extended to a more
+[general fee payment design](https://github.com/cosmos/ibc/issues/578) that could be adopted by any ICS application
+protocol.
+
+In general, the Interchain dream will never scale unless there is a clear way to incentivize relayers. We seek to
+define a clear interface that can be easily adopted by any application, but not preclude chains that don't use tokens.
+
+### Desired Properties
+
+- Incentivize timely delivery of the packet (`recvPacket` called)
+- Incentivize relaying acks for these packets (`acknowledgePacket` called)
+- Incentivize relaying timeouts for these packets when the timeout has expired before packet is delivered (for example as receive fee was too low) (`timeoutPacket` called)
+- Produces no extra IBC packets
+- One direction works, even when destination chain does not support concept of fungible tokens
+- Opt-in for each chain implementing this. e.g. ICS27 with fee support on chain A could connect to ICS27 without fee support on chain B.
+- Standardized interface for each chain implementing this extension
+- Support custom fee-handling logic within the same framework
+- Relayer addresses should not be forgeable
+- Enable permissionless or permissioned relaying
+
+### Definitions
+
+`forward relayer`: The relayer that submits the `recvPacket` message for a given packet
+
+`reverse relayer`: The relayer that submits the `acknowledgePacket` message for a given packet
+
+`timeout relayer`: The relayer that submits the `timeoutPacket` or `timeoutOnClose` message for a given packet
+
+`receive fee`: The fee paid for submitting the `recvPacket` message for a given packet
+
+`ack fee`: The fee paid for submitting the `acknowledgePacket` message for a given packet
+
+`timeout fee`: The fee paid for submitting the `timeoutPacket` or `timeoutOnClose` message for a given packet
+
+`source address`: The payee address selected by a relayer on the chain that sent the packet
+
+`destination address`: The address of a relayer on the chain that receives the packet
+
+## Technical Specification
+
+### General Design
+
+In order to avoid extra fee packets on the order of the number of application packets, as well as provide an opt-in approach, we
+store all fee payment info only on the source chain. The source chain is the one location where the sender can provide tokens
+to incentivize the packet. The fee distribution may be implementation specific and thus does not need to be in the IBC spec
+(just high-level requirements are needed in this doc).
+
+We require that the [relayer address is exposed to application modules](https://github.com/cosmos/ibc/pull/579) for
+all packet-related messages, so the modules are able to incentivize the packet relayer. `acknowledgePacket`, `timeoutPacket`,
+and `timeoutOnClose` messages will therefore have the relayer address and be capable of sending escrowed tokens to such address.
+However, we need a way to reliably get the address of the relayer that submitted `recvPacket` on the destination chain to
+the source chain. In fact, we need a *source address* for this relayer to pay out to, not the *destination address* that signed
+the packet.
+
+The fee payment mechanism will be implemented as IBC Middleware (see ICS-30) in order to provide maximum flexibility for application developers and blockchains.
+
+Given this, the flow would be:
+
+1. Relayer registers their destination address to source address mapping on the destination chain's fee middleware.
+1. User/module submits a send packet on the `source` chain, along with a message to the fee middleware module with some tokens and fee information on how to distribute them. The fee tokens are all escrowed by the fee module.
+1. RelayerA submits `RecvPacket` on the `destination` chain.
+1. Destination fee middleware will retrieve the source address for the given relayer's destination address (this mapping is already registered) and include it in the acknowledgement.
+1. RelayerB submits `AcknowledgePacket` which provides the *reverse relayer* address on the source chain in the message sender, along with the source address of the *forward relayer* embedded in the acknowledgement.
+1. Source fee middleware can distribute the tokens escrowed in (1) to both the *forward* and the *reverse* relayers and refund remainder tokens to original fee payer(s).
+
+Alternate flow:
+
+1. User/module submits a send packet on the `source` chain, along with some tokens and fee information on how to distribute them
+1. Relayer submits `OnTimeout` which provides its address on the source chain
+1. Source application can distribute the tokens escrowed in (1) to this relayer, and potentially return remainder tokens to the original fee payer(s).
+
+### Fee details
+
+For an example implementation in the Cosmos SDK, we consider 3 potential fee payments, which may be defined. Each one may be
+paid out in a different token. Imagine a connection between IrisNet and the Cosmos Hub. To incentivize a packet from IrisNet to the Cosmos Hub, they may define:
+
+- ReceiveFee: 0.003 channel-7/ATOM vouchers (ATOMs already on IrisNet via ICS20)
+- AckFee: 0.001 IRIS
+- TimeoutFee: 0.002 IRIS
+
+Ideally the fees can easily be redeemed in native tokens on both sides, but relayers may select others. In this example, the relayer collects a fair bit of IRIS, covering its costs there and more. It also collects channel-7/ATOM vouchers from many packets. After relaying a few thousand packets, the account on the Cosmos Hub is running low, so the relayer will send those channel-7/ATOM vouchers back over channel-7 to it's account on the Hub to replenish the supply there.
+
+The sender chain will escrow 0.003 channel-7/ATOM and 0.002 IRIS from the fee payers' account. In the case that a forward relayer submits the `recvPacket` and a reverse relayer submits the `ackPacket`, the forward relayer is rewarded 0.003 channel-7/ATOM and the reverse relayer is rewarded 0.001 IRIS while 0.002 IRIS is refunded to the original fee payer. In the case where the packet times out, the timeout relayer receives 0.002 IRIS and 0.003 channel-7/ATOM is refunded to the original fee payer.
+
+The logic involved in collecting fees from users and then paying it out to the relevant relayers is encapsulated by a separate fee module and may vary between implementations. However, all fee modules must implement a uniform interface such that the ICS-4 handlers can correctly pay out fees to the right relayers, and so that relayers themselves can easily determine the fees they can expect for relaying a packet.
+
+### Data Structures
+
+The incentivized acknowledgment written on the destination chain includes:
+
+- raw bytes of the acknowledgement from the underlying application,
+- the source address of the forward relayer,
+- and a boolean indicative of receive operation success on the underlying application.
+
+```typescript
+interface Acknowledgement {
+ appAcknowledgement: []byte
+ forwardRelayerAddress: string
+ underlyingAppSuccess: boolean
+}
+```
+
+### Store Paths
+
+#### Relayer Address for Async Ack Path
+
+The forward relayer addresses are stored under a store path prefix unique to a combination of port identifier, channel identifier and sequence. This may be stored in the private store.
+
+```typescript
+function relayerAddressForAsyncAckPath(packet: Packet): Path {
+ return "forwardRelayer/{packet.destPort}/{packet.destChannel}/{packet.sequence}"
+}
+```
+
+### Fee Middleware Contract
+
+While the details may vary between fee modules, all fee modules **must** ensure they does the following:
+
+- It must allow relayers to register their counterparty payee address (i.e. source address).
+- It must have in escrow the maximum fees that all outstanding packets may pay out (or it must have ability to mint required amount of tokens)
+- It must pay the receive fee for a packet to the forward relayer specified in `PayFee` callback (if unspecified, it must refund forward fee to original fee payer(s))
+- It must pay the ack fee for a packet to the reverse relayer specified in `PayFee` callback
+- It must pay the timeout fee for a packet to the timeout relayer specified in `PayTimeoutFee` callback
+- It must refund any remainder fees in escrow to the original fee payer(s) if applicable
+
+```typescript
+// RegisterCounterpartyPayee is called by the relayer on each channelEnd and
+// allows them to specify their counterparty payee address before relaying.
+// This ensures they will be properly compensated for forward relaying since
+// destination chain must send back relayer's source address (counterparty
+// payee address) in acknowledgement.
+// This function may be called more than once by relayer, in which case, latest
+// counterparty payee address is always used.
+function RegisterCounterpartyPayee(relayer: string, counterPartyAddress: string) {
+ // set mapping between relayer address and counterparty payee address
+}
+
+// EscrowPacketFee is an open callback that may be called by any module/user
+// that wishes to escrow funds in order to incentivize the relaying of the
+// given packet.
+// NOTE: These fees are escrowed in addition to any previously escrowed amount
+// for the packet. In the case where the previous amount is zero, the provided
+// fees are the initial escrow amount.
+// They may set a separate receiveFee, ackFee, and timeoutFee to be paid
+// for each step in the packet flow. The caller must send max(receiveFee+ackFee, timeoutFee)
+// to the fee module to be locked in escrow to provide payout for any potential
+// packet flow.
+// The caller may optionally specify an array of relayer addresses. This MAY be
+// used by the fee module to modify fee payment logic based on ultimate relayer
+// address. For example, fee module may choose to only pay out relayer if the
+// relayer address was specified in the `EscrowPacketFee`.
+function EscrowPacketFee(packet: Packet, receiveFee: Fee, ackFee: Fee, timeoutFee: Fee, relayers: []string) {
+ // escrow max(receiveFee+ackFee, timeoutFee) for this packet
+ // do custom logic with provided relayer addresses if necessary
+}
+
+// PayFee is a callback implemented by fee module called by the ICS-4 AcknowledgePacket handler.
+function PayFee(packet: Packet, forward_relayer: string, reverse_relayer: string) {
+ // pay the forward fee to the forward relayer address
+ // pay the reverse fee to the reverse relayer address
+ // refund extra tokens to original fee payer(s)
+ // NOTE: if forward relayer address is empty, then refund the forward fee to original fee payer(s).
+}
+
+// PayTimeoutFee is a callback implemented by fee module called by the ICS-4 TimeoutPacket handler.
+function PayTimeoutFee(packet: Packet, timeout_relayer: string) {
+ // pay the timeout fee to the timeout relayer address
+ // refund extra tokens to original fee payer(s)
+}
+```
+
+The fee module should also expose the following queries so that relayers may query their expected fee:
+
+```typescript
+// Gets the fee expected for submitting RecvPacket msg for the given packet
+// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s).
+function GetReceiveFee(portID, channelID, sequence, relayer) Fee
+
+// Gets the fee expected for submitting AcknowledgePacket msg for the given packet
+// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s).
+function GetAckFee(portID, channelID, sequence, relayer) Fee
+
+// Gets the fee expected for submitting TimeoutPacket msg for the given packet
+// Caller should provide the intended relayer address in case the fee is dependent on specific relayer(s).
+function GetTimeoutFee(portID, channelID, sequence, relayer) Fee
+```
+
+Since different chains may have different representations for fungible tokens and this information is not being sent to other chains; this ICS does not specify a particular representation for the `Fee`. Each chain may choose its own representation, it is incumbent on relayers to interpret the Fee correctly.
+
+A default representation will have the following structure:
+
+```typescript
+interface Fee {
+ denom: string,
+ amount: uint256,
+}
+```
+
+### IBC Module Wrapper
+
+The fee middleware will implement its own ICS-26 callbacks that wrap the application-specific module callbacks as well as the ICS-4 handler functions called by the underlying application. This fee middleware will ensure that the counterparty module supports incentivization and will implement all fee-specific logic. It will then pass on the request to the embedded application module for further callback processing.
+
+In this way, custom fee-handling logic can be hooked up to the IBC packet flow logic without placing the code in the ICS-4 handlers or the application code. This is valuable since the ICS-4 handlers should only be concerned with correctness of core IBC (transport, authentication, and ordering), and the application handlers should not be handling fee logic that is universal amongst all other incentivized applications. In fact, a given application module should be able to be hooked up to any fee module with no further changes to the application itself.
+
+#### Fee Protocol Negotiation
+
+The fee middleware will negotiate its fee protocol version with the counterparty module by including its own version next to the application version. The channel version will be a string of a JSON struct containing the fee middleware version and the application version. The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application.
+
+Channel Version:
+
+```json
+{"fee_version":"","app_version":""}
+```
+
+Ex:
+
+```json
+{"fee_version":"ics29-1","app_version":"ics20-1"}
+```
+
+The fee middleware's handshake callbacks ensure that both modules agree on compatible fee protocol version(s), and then pass the application-specific version string to the embedded application's handshake callbacks.
+
+#### Handshake Callbacks
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): (version: string, err: Error) {
+ if version != "" {
+ // try to unmarshal JSON-encoded version string and pass
+ // the app-specific version to app callback.
+ // otherwise, pass version directly to app callback.
+ metadata, err = UnmarshalJSON(version)
+ if err != nil {
+ // call the underlying applications OnChanOpenInit callback
+ return app.onChanOpenInit(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ version,
+ )
+ }
+
+ // check that feeVersion is supported
+ if !isSupported(metadata.feeVersion) {
+ return "", error
+ }
+ } else {
+ // enable fees by default if relayer does not specify otherwise
+ metadata = {
+ feeVersion: "ics29-1",
+ appVersion: "",
+ }
+ }
+
+ // call the underlying application's OnChanOpenInit callback.
+ // if the version string is empty, OnChanOpenInit is expected to return
+ // a default version string representing the version(s) it supports
+ appVersion, err = app.onChanOpenInit(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ metadata.appVersion,
+ )
+ if err != nil {
+ return "", err
+ }
+
+ // a new version string is constructed with the app version returned
+ // by the underlying application, in case it is different than the
+ // one passed by the caller
+ version = constructVersion(metadata.feeVersion, appVersion)
+
+ return version, nil
+}
+
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): (version: string, err: Error) {
+ // try to unmarshal JSON-encoded version string and pass
+ // the app-specific version to app callback.
+ // otherwise, pass version directly to app callback.
+ cpMetadata, err = UnmarshalJSON(counterpartyVersion)
+ if err != nil {
+ // call the underlying application's OnChanOpenTry callback
+ return app.onChanOpenTry(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ counterpartyVersion,
+ )
+ }
+
+ // select mutually compatible fee version
+ if !isCompatible(cpMetadata.feeVersion) {
+ return "", error
+ }
+ feeVersion = selectFeeVersion(cpMetadata.feeVersion)
+
+ // call the underlying application's OnChanOpenTry callback
+ appVersion, err = app.onChanOpenTry(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ cpMetadata.appVersion,
+ )
+ if err != nil {
+ return "", err
+ }
+
+ // a new version string is constructed with the final fee version
+ // that is selected and the app version returned by the underlying
+ // application (which may be different than the one passed by the caller)
+ version = constructVersion(feeVersion, appVersion)
+
+ return version, nil
+}
+
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ cpMetadata, err = UnmarshalJSON(counterpartyVersion)
+ if err != nil {
+ // call the underlying application's OnChanOpenAck callback
+ return app.onChanOpenAck(
+ portIdentifier,
+ channelIdentifier,
+ counterpartyChannelIdentifier,
+ counterpartyVersion,
+ )
+ }
+
+ if !isSupported(cpMetadata.feeVersion) {
+ return error
+ }
+ // call the underlying application's OnChanOpenAck callback
+ return app.onChanOpenAck(
+ portIdentifier,
+ channelIdentifier,
+ counterpartyChannelIdentifier,
+ cpMetadata.appVersion,
+ )
+}
+
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // fee middleware performs no-op on ChanOpenConfirm,
+ // just call underlying callback
+ return app.onChanOpenConfirm(portIdentifier, channelIdentifier)
+}
+```
+
+#### Packet Callbacks
+
+```typescript
+function onRecvPacket(packet: Packet, relayer: string): bytes {
+ app_acknowledgement = app.onRecvPacket(packet, relayer)
+
+ // in case of asynchronous acknowledgement, we must store the relayer
+ // address. It will be retrieved later and used to get the source
+ // address that will be written in the acknowledgement.
+ if app_acknowledgement == nil {
+ privateStore.set(relayerAddressForAsyncAckPath(packet), relayer)
+ }
+
+ // get source address by retrieving counterparty payee address of
+ // this relayer stored in fee middleware.
+ // NOTE: source address may be empty or invalid, counterparty
+ // must refund fee in these cases
+ sourceAddress = getCounterpartyPayeeAddress(relayer)
+
+ // wrap the acknowledgement with forward relayer and return marshalled bytes
+ // constructIncentivizedAck takes:
+ // - the app-specific acknowledgement,
+ // - the receive-packet relayer (forward relayer)
+ // - and a boolean indicative of receive operation success,
+ // and constructs the incentivized acknowledgement struct with
+ // the forward relayer and app-specific acknowledgement embedded.
+ ack = constructIncentivizedAck(app_acknowledgment, sourceAddress, app_acknowledgment.success)
+ return marshal(ack)
+}
+
+function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) {
+ // the acknowledgement is a marshalled struct containing:
+ // - the forward relayer address as a string (called forward_relayer)
+ // - and the raw acknowledgement bytes returned by the counterparty application module (called app_ack).
+
+ // get the forward relayer from the (incentivized) acknowledgement
+ // and pay fees to forward and reverse relayers.
+ // reverse_relayer is submitter of acknowledgement message
+ // provided in function arguments
+ // NOTE: Fee may be zero
+ ack = unmarshal(acknowledgement)
+ forward_relayer = getForwardRelayer(ack)
+ PayFee(packet, forward_relayer, relayer)
+
+ // unwrap the raw acknowledgement bytes sent by counterparty application
+ // and pass it to the application callback.
+ app_ack = getAppAcknowledgement(acknowledgement)
+
+ app.OnAcknowledgePacket(packet, app_ack, relayer)
+}
+
+function onTimeoutPacket(packet: Packet, relayer: string) {
+ // get the timeout relayer from function arguments
+ // and pay timeout fee.
+ // NOTE: Fee may be zero
+ PayTimeoutFee(packet, relayer)
+ app.OnTimeoutPacket(packet, relayer)
+}
+
+function onTimeoutPacketClose(packet: Packet, relayer: string) {
+ // get the timeout relayer from function arguments
+ // and pay timeout fee.
+ // NOTE: Fee may be zero
+ PayTimeoutFee(packet, relayer)
+ app.onTimeoutPacketClose(packet, relayer)
+}
+
+function constructIncentivizedAck(
+ app_ack: bytes,
+ forward_relayer: string,
+ success: boolean): Acknowledgement {
+ return Acknowledgement{
+ appAcknowledgement: app_ack,
+ forwardRelayerAddress: relayer,
+ underlyingAppSuccess: success,
+ }
+}
+
+function getForwardRelayer(ack: Acknowledgement): string {
+ ack.forwardRelayerAddress
+}
+
+function getAppAcknowledgement(ack: Acknowledgement): bytes {
+ ack.appAcknowledgement
+}
+```
+
+#### Embedded applications calling into ICS-4
+
+Note that if the embedded application uses asynchronous acks then, the `WriteAcknowledgement` call in the application must call the fee middleware's `WriteAcknowledgement` rather than calling the ICS-4 handler's `WriteAcknowledgement` function directly.
+
+```typescript
+// Fee Middleware writeAcknowledgement function
+function writeAcknowledgement(
+ packet: Packet,
+ acknowledgement: bytes) {
+ // retrieve the relayer that was stored in `onRecvPacket`
+ relayer = privateStore.get(relayerAddressForAsyncAckPath(packet))
+ // get source address by retrieving counterparty payee address
+ // of this relayer stored in fee middleware.
+ sourceAddress = getCounterpartyPayeeAddress(relayer)
+ ack = constructIncentivizedAck(acknowledgment, sourceAddress, acknowledgment.success)
+ ack_bytes = marshal(ack)
+ // ics4Wrapper may be core IBC or higher-level middleware
+ return ics4Wrapper.writeAcknowledgement(packet, ack_bytes)
+}
+
+// Fee Middleware sendPacket function just forwards data to ics-4 handler
+function sendPacket(
+ capability: CapabilityKey,
+ sourcePort: Identifier,
+ sourceChannel: Identifier,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64,
+ data: bytes): uint64 {
+ // ics4Wrapper may be core IBC or higher-level middleware
+ return ics4Wrapper.sendPacket(
+ capability,
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ data)
+}
+```
+
+### User Interaction with Fee Middleware
+
+**User sending Packets**
+
+A user may specify a fee to incentivize the relaying during packet submission, by submitting a fee payment message atomically with the application-specific "send packet" message (e.g. ICS-20 `MsgTransfer`). The fee middleware will escrow the fee for the packet that is created atomically with the escrow. The fee payment message itself is not specified in this document as it may vary greatly across implementations. In some middleware, there may be no fee payment message at all if the fees are being paid out from an altruistic pool.
+
+Since the fee middleware does not need to modify the outgoing packet, the fee payment message may be placed before or after the send packet message. However in order to maintain consistency with other middleware messages, it is recommended that fee middleware require their messages to be placed before the send packet message and escrow fees for the **next sequence** on the given channel. This way when the messages are atomically committed, the next sequence on the channel is the send packet message sent by the user, and the user escrows their fee for the created packet.
+
+In case a user wants to pay fees on a packet after it has already been created, the fee middleware SHOULD provide a message that allows users to pay fees on a packet with the specified sequence, channel and port identifiers. This allows the user to uniquely identify a packet that has already been created, so that the fee middleware can escrow fees for that packet after the fact.
+
+**Relayers sending RecvPacket**
+
+Before a relayer starts relaying on a channel, they should register their counterparty message using the standardized message:
+
+```typescript
+interface RegisterCounterpartyPayeeMsg {
+ portID: string
+ channelID: string
+ relayer: string // destination address of the forward relayer
+ counterpartyPayee: string // source address of the forward relayer
+}
+```
+
+It is the responsibility of the receiving chain to authenticate that the message was received from owner of `relayer`. The receiving chain must store the mapping from: `relayer -> counterpartyPayee` for the given channel. Then, `onRecvPacket` of the destination fee middleware can query for the counterparty payee address of the `recvPacket` message sender in order to get the source address of the forward relayer. This source address is what will get embedded in the acknowledgement.
+
+If the relayer does not register their counterparty payee address (or registers an invalid address), then the acknowledgment will still be received and processed but the forward fee will be refunded to the original fee payer(s).
+
+#### Backwards Compatibility
+
+Maintaining backwards compatibility with an unincentivized chain directly in the fee module, would require the top-level fee module to negotiate versions that do not contain a fee version and communicate with both incentivized and unincentivized modules. This pattern causes unnecessary complexity as the layers of nested applications increase.
+
+Instead, the fee module will only connect to a counterparty fee module. This simplifies the fee module logic, and doesn't require it to mimic the underlying nested application(s).
+
+In order for an incentivized chain to maintain backwards compatibility with an unincentivized chain for a given application (e.g. ICS-20), the incentivized chain should host both a top-level ICS-20 module and a top-level fee module that nests an ICS-20 application each of which should bind to unique ports.
+
+#### Reasoning
+
+This proposal satisfies the desired properties. All parts of the packet flow (receive/acknowledge/timeout) can be properly incentivized and rewarded. The protocol does not specify the relayer beforehand, thus the incentivization can be permissionless or permissioned. The escrowing and distribution of funds is completely handled on source chain, thus there is no need for additional IBC packets or the use of ICS-20 in the fee protocol. The fee protocol only assumes existence of fungible tokens on the source chain. By creating application stacks for the same base application (one with fee middleware, one without), we can get backwards compatibility.
+
+##### Correctness
+
+The fee module is responsible for correctly escrowing and distributing funds to the provided relayers. The ack and timeout relayers are trivially retrievable since they are the senders of the acknowledgment and timeout message. The forward relayer is responsible for registering their source address before sending `recvPacket` messages, so that the destination fee middleware can embed this address in the acknowledgement. The fee middleware on source will then use the address in acknowledgement to pay the forward relayer on the source chain.
+
+The source chain will use a "best efforts" approach with regard to the forward relayer address. Since it is not verified directly by the counterparty and is instead just treated as a string to be passed back in the acknowledgement, the registered forward relayer source address may not be a valid source chain address. In this case, the invalid address is discarded, the receive fee is refunded, and the acknowledgement processing continues. It is incumbent on relayers to register their source addresses to the counterparty chain correctly.
+In the event that the counterparty chain itself incorrectly sends the forward relayer address, this will cause relayers to not collect fees on source chain for relaying packets. The incentivize-driven relayers will stop relaying for the chain until the acknowledgement logic is fixed, however the channel remains functional.
+
+We cannot return an error on an invalid source address as this would permanently prevent the source chain from processing the acknowledgment of a packet that was otherwise correctly received, processed and acknowledged on the counterparty chain. The IBC protocol requires that incorrect or malicious relayers may at best affect the liveness of a user's packets. Preventing successful acknowledgement in this case would leave the packet flow at a permanently incomplete state, which may be very consequential for certain IBC applications like ICS-20.
+
+Thus, the forward relayer reward is contingent on it providing the correct `payOnSender` address when it sends the `receive_packet` message. The packet flow will continue processing successfully even if the fee payment is unsuccessful.
+
+With the forward relayer correctly embedded in the acknowledgement, and the reverse and timeout relayers available directly in the message; the fee middleware will accurately escrow and distribute fee payments to the relevant relayers.
+
+#### Optional addenda
+
+## Forwards Compatibility
+
+Not applicable.
+
+## Example Implementations
+
+- Implementation of ICS 29 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## History
+
+June 8 2021 - Switched to middleware solution from implementing callbacks in ICS-4 directly.
+
+June 1 2021 - Draft written
+
+July 6, 2022 - Update with latest changes from implementation
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-030-middleware/README.mdx b/ibc/next/spec/app/ics-030-middleware/README.mdx
new file mode 100644
index 00000000..b615298e
--- /dev/null
+++ b/ibc/next/spec/app/ics-030-middleware/README.mdx
@@ -0,0 +1,372 @@
+---
+ics: 30
+title: IBC Middleware
+stage: draft
+category: IBC/APP
+requires: [4, 25, 26]
+kind: instantiation
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Aditya Sripal
+ email: aditya@interchain.berlin
+ - name: Ethan Frey
+ email: ethan@confio.tech
+created: 2021-06-01
+modified: 2022-07-06
+---
+
+
+## Synopsis
+
+This standard documents specifies the interfaces and state machine logic that a module must implement in order to act as middleware between core IBC and an underlying application(s). IBC Middleware will enable arbitrary extensions to an application's functionality without requiring changes to the application or core IBC.
+
+### Motivation
+
+IBC applications are designed to be self-contained modules that implement their own application-specific logic through a set of interfaces with the core IBC handlers. These core IBC handlers, in turn, are designed to enforce the correctness properties of IBC (transport, authentication, ordering) while delegating all application-specific handling to the IBC application modules. However, there are cases where some functionality may be desired by many applications, yet not appropriate to place in core IBC. The most prescient example of this, is the generalized fee payment protocol. Most applications will want to opt in to a protocol that incentivizes relayers to relay packets on their channel. However, some may not wish to enable this feature and yet others will want to implement their own custom fee handler.
+
+Without a middleware approach, developers must choose whether to place this extension in application logic inside each relevant application; or place the logic in core IBC. Placing it in each application is redundant and prone to error. Placing the logic in core IBC requires an opt-in from all applications and violates the abstraction barrier between core IBC (TAO) and the application. Either case is not scalable as the number of extensions increase, since this must either increase code bloat in applications or core IBC handlers.
+
+Middleware allows developers to define the extensions as separate modules that can wrap over the base application. This middleware can thus perform its own custom logic, and pass data into the application so that it may run its logic without being aware of the middleware's existence. This allows both the application and the middleware to implement its own isolated logic while still being able to run as part of a single packet flow.
+
+### Definitions
+
+`Middleware`: A self-contained module that sits between core IBC and an underlying IBC application during packet execution. All messages between core IBC and underlying application must flow through middleware, which may perform its own custom logic.
+
+`Underlying Application`: An underlying application is the application that is directly connected to the middleware in question. This underlying application may itself be middleware that is chained to a base application.
+
+`Base Application`: A base application is an IBC application that does not contain any middleware. It may be nested by 0 or multiple middleware to form an application stack.
+
+`Application Stack (or stack)`: A stack is the complete set of application logic (middleware(s) + base application) that gets connected to core IBC. A stack may be just a base application, or it may be a series of middlewares that nest a base application.
+
+### Desired Properties
+
+- Middleware enables arbitrary extensions of application logic
+- Middleware can be arbitrarily nested to create a chain of app extensions
+- Core IBC does not need to change
+- Base Application logic does not need to change
+
+## Technical Specification
+
+### General Design
+
+In order to function as IBC Middleware, a module must implement the IBC application callbacks and pass along the pre-processed data to the nested application. It must also implement `WriteAcknowledgement` and `SendPacket`, which will be called by the end application, so that it may post-process the information before passing data along to core ibc.
+
+When nesting an application, the module must make sure that it is in the middle of communication between core IBC and the application in both directions. Developers should do this by registering the top-level module directly with the IBC router (not any nested applications). The nested applications in turn, must be given access only to the middleware's `WriteAcknowledgement` and `SendPacket` rather than to the core IBC handlers directly.
+
+Additionally, the middleware must take care to ensure that the application logic can execute its own version negotiation without interference from the nesting middleware. In order to do this, the middleware will format the version in a JSON-encoded string containing the middleware version and the application version (and potentially also other custom parameter fields). The application version may as well be a JSON-encoded string, possibly including further middleware and app versions, if the application stack consists of multiple milddlewares wrapping a base application. The format of the version string is as follows:
+
+```json
+{
+ "": "",
+ "app_version": "",
+ // ... other custom parameter fields
+}
+```
+
+The `` key in the JSON struct should be replaced by the actual name of the key for the corresponding middleware (e.g. `fee_version` for ICS-29 fee middleware).
+
+In the application callbacks, the middleware can unmarshal the version string and retrieve the middleware and application versions. It must do its own version negotiation on `` and then hand over `` to the nested application's callback. This is only relevant if the middleware expects a compatible counterparty middleware at the same level on the counterparty stack. Middleware that only executes on a single side of the channel MUST NOT modify the channel version.
+
+Each application stack must reserve its own unique port with core IBC. Thus two stacks with the same base application must bind to separate ports.
+
+#### Interfaces
+
+```typescript
+// Middleware implements the ICS26 Module interface
+interface Middleware extends ICS26Module {
+ // middleware has access to an underlying application which may be wrapped
+ // by more middleware.
+ app: ICS26Module
+ // middleware has access to ICS4Wrapper which may be core IBC Channel Handler
+ // or a higher-level middleware that wraps this middleware.
+ ics4Wrapper: ICS4Wrapper
+}
+```
+
+```typescript
+// This is implemented by ICS4 and all middleware that are wrapping base application.
+// The base application will call `sendPacket` or `writeAcknowledgement` of the
+// middleware directly above them which will call the next middleware until it reaches
+// the core IBC handler.
+interface ICS4Wrapper {
+ sendPacket(
+ capability: CapabilityKey,
+ sourcePort: Identifier,
+ sourceChannel: Identifier,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64,
+ data: bytes): uint64
+ writeAcknowledgement(packet: Packet, ack: Acknowledgement)
+}
+```
+
+#### Handshake Callbacks
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): (version: string, err: Error) {
+ if version != "" {
+ // try to unmarshal JSON-encoded version string and pass
+ // the app-specific version to app callback.
+ // otherwise, pass version directly to app callback.
+ metadata, err = UnmarshalJSON(version)
+ if err != nil {
+ // call the underlying application's onChanOpenInit callback
+ return app.onChanOpenInit(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ version,
+ )
+ }
+ } else {
+ metadata = {
+ // set middleware version to default value
+ middlewareVersion: defaultMiddlewareVersion,
+ // allow application to return its default version
+ appVersion: "",
+ }
+ }
+
+ doCustomLogic()
+
+ // call the underlying application's OnChanOpenInit callback.
+ // if the version string is empty, OnChanOpenInit is expected to return
+ // a default version string representing the version(s) it supports
+ appVersion, err = app.OnChanOpenInit(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ metadata.appVersion, // note we only pass app version here
+ )
+ abortTransactionUnless(err != nil)
+
+ // a new version string is constructed with the app version returned
+ // by the underlying application, in case it is different than the
+ // one passed by the caller
+ metadata = {
+ // note this should have a different field name specific to middleware
+ middlewareVersion: metadata.middlewareVersion,
+ appVersion: appVersion,
+ }
+
+ return MarshallJSON(metadata), nil
+}
+
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): (version: string, err: Error) {
+ // try to unmarshal JSON-encoded version string and pass
+ // the app-specific version to app callback.
+ // otherwise, pass version directly to app callback.
+ cpMetadata, err = UnmarshalJSON(counterpartyVersion)
+ if err != nil {
+ // call the underlying application's OnChanOpenTry callback
+ return app.onChanOpenTry(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ counterpartyVersion,
+ )
+ }
+
+ // select mutually compatible middleware version
+ if !isCompatible(cpMetadata.middlewareVersion) {
+ return "", error
+ }
+ middlewareVersion = selectMiddlewareVersion(cpMetadata.middlewareVersion)
+
+ doCustomLogic()
+
+ // call the underlying application's OnChanOpenTry callback
+ appVersion, err = app.OnChanOpenTry(
+ order,
+ connectionHops,
+ portIdentifier,
+ channelIdentifier,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ cpMetadata.appVersion, // note we only pass counterparty app version here
+ )
+ abortTransactionUnless(err != nil)
+
+ // a new version string is constructed with the final middleware version
+ // that is selected and the app version returned by the underlying
+ // application (which may be different than the one passed by the caller)
+ metadata = {
+ // note this should have a different field name specific to middleware
+ middlewareVersion: middlewareVersion,
+ appVersion: appVersion,
+ }
+
+ return MarshalJSON(metadata), nil
+}
+
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ cpMetadata, err = UnmarshalJSON(counterpartyVersion)
+ if err != nil {
+ // call the underlying application's OnChanOpenAck callback
+ return app.onChanOpenAck(
+ portIdentifier,
+ channelIdentifier,
+ counterpartyChannelIdentifier,
+ counterpartyVersion,
+ )
+ }
+
+ if !isSupported(cpMetadata.middlewareVersion) {
+ return error
+ }
+ doCustomLogic()
+
+ // call the underlying application's OnChanOpenAck callback
+ return app.onChanOpenAck(
+ portIdentifier,
+ channelIdentifier,
+ counterpartyChannelIdentifier,
+ cpMetadata.appVersion,
+ )
+}
+
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ doCustomLogic()
+ app.OnChanOpenConfirm(portIdentifier, channelIdentifier)
+}
+```
+
+NOTE: Middleware that does not need to negotiate with a counterparty middleware on the remote stack will not implement the version unmarshaling and negotiation, and will simply perform its own custom logic on the callbacks without relying on the counterparty behaving similarly.
+
+#### Packet Callbacks
+
+```typescript
+function onRecvPacket(packet: Packet, relayer: string): bytes {
+ doCustomLogic()
+
+ app_acknowledgement = app.onRecvPacket(packet, relayer)
+
+ // middleware may modify ack
+ ack = doCustomLogic(app_acknowledgement)
+
+ return marshal(ack)
+}
+
+function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) {
+ doCustomLogic()
+
+ // middleware may modify ack
+ app_ack = getAppAcknowledgement(acknowledgement)
+
+ app.onAcknowledgePacket(packet, app_ack, relayer)
+
+ doCustomLogic()
+}
+
+function onTimeoutPacket(packet: Packet, relayer: string) {
+ doCustomLogic()
+
+ app.onTimeoutPacket(packet, relayer)
+
+ doCustomLogic()
+}
+
+function onTimeoutPacketClose(packet: Packet, relayer: string) {
+ doCustomLogic()
+
+ app.onTimeoutPacketClose(packet, relayer)
+
+ doCustomLogic()
+}
+```
+
+NOTE: Middleware may do pre- and post-processing on underlying application data for all IBC Module callbacks defined in ICS-26.
+
+#### ICS-4 Wrappers
+
+```typescript
+function writeAcknowledgement(
+ packet: Packet,
+ acknowledgement: bytes) {
+ // middleware may modify acknowledgement
+ ack_bytes = doCustomLogic(acknowledgement)
+
+ return ics4Wrapper.writeAcknowledgement(packet, ack_bytes)
+}
+```
+
+```typescript
+function sendPacket(
+ capability: CapabilityKey,
+ sourcePort: Identifier,
+ sourceChannel: Identifier,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64,
+ app_data: bytes): uint64 {
+ // middleware may modify packet
+ data = doCustomLogic(app_data)
+
+ return ics4Wrapper.sendPacket(
+ capability,
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ data)
+}
+```
+
+### User Interaction
+
+In the case where the middleware requires some user input in order to modify the outgoing packet messages from the underlying application, the middleware MUST get this information from the user before it receives the packet message from the underlying application. It must then do its own authentication of the user input, and ensure that the user input provided to the middleware is matched to the correct outgoing packet message. The middleware MAY accomplish this by requiring that the user input to middleware, and packet message to underlying application are sent atomically and ordered from outermost middleware to base application.
+
+### Security Model
+
+As seen above, IBC middleware may arbitrarily modify any incoming or outgoing data from an underlying application. Thus, developers should not use any untrusted middleware in their application stacks.
+
+## Backwards Compatibility
+
+The Middleware approach is a design pattern already enabled by current IBC. This ICS seeks to standardize a particular design pattern for IBC middleware. There are no changes required to core IBC or any existing application.
+
+## Forwards Compatibility
+
+Not applicable.
+
+## Example Implementations
+
+- Implementation of ICS 29 in Go following ICS 30 design pattern can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## History
+
+June 22, 2021 - Draft submitted
+
+July 6, 2022 - Update with latest changes from implementation
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-031-crosschain-queries/README.mdx b/ibc/next/spec/app/ics-031-crosschain-queries/README.mdx
new file mode 100644
index 00000000..176f0b3e
--- /dev/null
+++ b/ibc/next/spec/app/ics-031-crosschain-queries/README.mdx
@@ -0,0 +1,414 @@
+---
+ics: 31
+title: Cross-chain Queries
+stage: draft
+category: IBC/APP
+requires: [2, 5, 18, 23, 24]
+kind: instantiation
+authors:
+ - name: Joe Schnetzler
+ email: schnetzlerjoe@gmail.com
+ - name: Manuel Bravo
+ email: manuel@informal.systems
+created: 2022-01-06
+modified: 2022-07-28
+---
+
+## Synopsis
+
+This standard document specifies the data structures and state machine handling logic of the Cross-chain Queries module, which allows for cross-chain querying between IBC enabled chains.
+
+## Overview and Basic Concepts
+
+### Motivation
+
+We expect on-chain applications to depend on reads from other chains, e.g., a particular application on a chain may need to know the current price of the token of a second chain. While the IBC protocol enables on-chain applications to talk to other chains, using it for simply querying the state of chains would be too expensive: it would require to maintain an open channel between the querying chain and any other chain, and use the full IBC stack for every query request. Note that the latter implies exchanging packets between chains and therefore committing transactions at the queried chain, which may disrupt its operation if the load of query requests is high. Cross-chain queries solve this issue. It enables on-chain applications to query the state of other chains seamlessly: without involving the queried chain, and requiring very little from the querying chain.
+
+### Definitions
+
+`Querying chain`: The chain that is interested in getting data from another chain (queried chain). The querying chain is the chain that implements the Cross-chain Queries module.
+
+`Queried chain`: The chain whose state is being queried. The queried chain gets queried via a relayer utilizing its RPC client which is then submitted back to the querying chain.
+
+`Cross-chain Queries Module`: The module that implements the cross-chain querying protocol. Only the querying chain integrates it.
+
+`Height` and client-related functions are as defined in ICS 2.
+
+`newCapability` and `authenticateCapability` are as defined in ICS 5.
+
+`CommitmentPath` and `CommitmentProof` are as defined in ICS 23.
+
+`Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in ICS 24.
+
+`Fee` is as defined in ICS 29.
+
+## System Model and Properties
+
+### Assumptions
+
+- **Safe chains:** Both the querying and queried chains are safe. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol.
+
+- **Live chains:** Both the querying and queried chains MUST be live, i.e., new blocks are eventually added to the chain.
+
+- **Censorship-resistant querying chain:** The querying chain cannot selectively omit valid transactions.
+
+> For example, this means that if a relayer submits a valid transaction to the querying chain, the transaction is guaranteed to be eventually included in a committed block. Note that Tendermint does not currently guarantee this.
+
+- **Correct relayer:** There is at least one live relayer between the querying and queried chains where the relayer correctly follows the protocol.
+
+> In the context of this specification, this implies that for every query request coming from the querying chain, there is at least one relayer that (i) picks the query request up, (ii) executes the query at the queried chain, and (iii) submits the result in a transaction, together with a valid proof, to the querying chain.
+
+The above assumptions are enough to guarantee that the query protocol returns results to the application if the querying chain waits unboundly for query results. Nevertheless, this specification considers the case when the querying chain times out after a fixed period of time. Thus, to guarantee that the query protocol always returns query results to the application, the
+specification requires additional assumptions: both the querying chain and at least one correct relayer have to behave timely.
+
+- **Timely querying chain:** There exists an upper-bound in the time elapsed between the moment a transaction is submitted to the chain and when the chain commits a block including it.
+
+- **Timely relayer:** For correct and live relayers, there exists an upper-bound in the time elapsed between the moment a relayer picks a query request and when the relayer submits the query result.
+
+> Note then that to guarantee that the query protocol always returns results to the application, the timeout bound at the querying chain should be at least equal to the sum of the upper-bounds of assumptions **Timely querying chain** and **Timely relayer**. This would guarantee that the relayer submits and the querying chain process a query result transaction within the specified timeout bound.
+
+### Desired Properties
+
+#### Permissionless
+
+The querying chain can query a chain without permission from the latter and implement cross-chain querying without any approval from a third party or chain governance. Note that since there is no prior negotiation between chains, the querying chain cannot assume that queried data will be in an expected format.
+
+#### Minimal queried chain Work
+
+Any chain that provides query support can act as a queried chain, requiring no implementation work or any extra module. This is possible by utilizing an RPC client on a relayer.
+
+#### Modular
+
+Supporting cross-chain queries should be as easy as implementing a module in your chain.
+
+#### Incentivization
+
+A bounty is paid to incentivize relayers for participating in cross-chain queries: fetching data from the queried chain and submitting it (together with proofs) to the querying chain.
+
+## Technical Specification
+
+### General Design
+
+The querying chain must implement the Cross-chain Queries module, which allows the querying chain to query state at the queried chain.
+
+Cross-chain queries rely on relayers operating between both chains. When a query request is received by the querying chain, the Cross-chain Queries module emits a `sendQuery` event. Relayers operating between the querying and queried chains must monitor the querying chain for `sendQuery` events. Eventually, a relayer will retrieve the query request and execute it, i.e., fetch the data and generate the corresponding proofs, at the queried chain. The relayer then submits the result in a transaction to the querying chain. The result is finally registered at the querying chain by the Cross-chain Queries module.
+
+A query request includes the height of the queried chain at which the query must be executed. The reason is that the keys being queried can have different values at different heights. Thus, a malicious relayer could choose to query a height that has a value that benefits it somehow. By letting the querying chain decide the height at which the query is executed, we can prevent relayers from affecting the result data.
+
+> Note that this mechanism does not prevent cross-chain MEV (maximal extractable value): this still creates an opportunity for altering the state on the queried chain if the height is in the future in order to change the results of the query.
+
+### Data Structures
+
+The Cross-chain Queries module stores query requests when it processes them.
+
+A CrossChainQuery is a particular interface to represent query requests. A request is retrieved when its result is submitted.
+
+```typescript
+interface CrossChainQuery {
+ id: Identifier
+ path: CommitmentPath
+ localTimeoutHeight: Height
+ localTimeoutTimestamp: uint64
+ queryHeight: Height
+ clientId: Identifier
+ bounty: Fee
+}
+```
+
+- The `id` field uniquely identifies the query at the querying chain.
+- The `path` field is the path to be queried at the queried chain.
+- The `localTimeoutHeight` field specifies a height limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller.
+- The `localTimeoutTimestamp` field specifies a timestamp limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller.
+- The `queryHeight` field is the height at which the relayer must query the queried chain
+- The `clientId` field identifies the querying chain's client of the queried chain.
+- The `bounty` field is a bounty that is given to the relayer for participating in the query.
+
+The Cross-chain Queries module stores query results to allow query callers to asynchronously retrieve them.
+In this context, this standard defines the `QueryResult` type as follows:
+
+```typescript
+enum QueryResult {
+ SUCCESS,
+ FAILURE,
+ TIMEOUT
+}
+```
+
+- A query that returns a value is marked as `SUCCESS`. This means that the query has been executed at the queried chain and there was a value associated to the queried path at the requested height.
+- A query that is executed but does not return a value is marked as `FAILURE`. This means that the query has been executed at the queried chain, but there was no value associated to the queried path at the requested height.
+- A query that timed out before a result is committed at the querying chain is marked as `TIMEOUT`.
+
+A `CrossChainQueryResult` is a particular interface used to represent query results.
+
+```typescript
+interface CrossChainQueryResult {
+ id: Identifier
+ result: QueryResult
+ data: []byte
+}
+```
+
+- The `id` field uniquely identifies the query at the querying chain.
+- The `result` field indicates whether the query was correctly executed at the queried chain and if the queried path exists.
+- The `data` field is an opaque bytestring that contains the value associated with the queried path in case `result = SUCCESS`.
+
+### Store paths
+
+#### Query path
+
+The query path is a private path that stores the state of ongoing cross-chain queries.
+
+```typescript
+function queryPath(id: Identifier): Path {
+ return "queries/{id}"
+}
+```
+
+#### Result query path
+
+The result query path is a private path that stores the result of completed queries.
+
+```typescript
+function queryResultPath(id: Identifier): Path {
+ return "result/queries/{id}"
+}
+```
+
+### Helper functions
+
+The querying chain MUST implement a function `generateIdentifier`, which generates a unique query identifier:
+
+```typescript
+function generateIdentifier = () -> Identifier
+```
+
+### Sub-protocols
+
+#### Query lifecycle
+
+1) When the querying chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Queries module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted as transactions to the querying chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. Typically, query requests will be issued by other IBC modules.
+2) A correct relayer listening to `sendQuery` events from the querying chain will eventually pick the query request up and execute it at the queried chain. The result is then submitted in a transaction to the querying chain.
+3) When the query result is committed at the querying chain, this calls the `CrossChainQueryResponse` function of the Cross-chain Queries module.
+4) The `CrossChainQueryResponse` first retrieves the query from the `privateStore` using the query's unique identifier. It then proceeds to verify the result using its local client. If it passes the verification, the function removes the query from the `privateStore` and stores the result in the private store.
+
+> The querying chain may execute additional state machine logic when a query result is received. To account for this additional state machine logic and charge a fee to the query caller, an implementation of this specification could use the already existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field.
+
+5) The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it.
+
+#### Normal path methods
+
+The `CrossChainQueryRequest` function is called when the Cross-chain Queries module at the querying chain receives a new query request.
+
+```typescript
+function CrossChainQueryRequest(
+ path: CommitmentPath,
+ queryHeight: Height,
+ localTimeoutHeight: Height,
+ localTimeoutTimestamp: uint64,
+ clientId: Identifier,
+ bounty: Fee,
+ ): [Identifier, CapabilityKey] {
+
+ // Check that there exists a client of the queried chain. The client will be used to verify the query result.
+ abortTransactionUnless(queryClientState(clientId) !== null)
+
+ // Sanity-check that localTimeoutHeight is 0 or greater than the current height, otherwise the query will always time out.
+ abortTransactionUnless(localTimeoutHeight === 0 || localTimeoutHeight > getCurrentHeight())
+ // Sanity-check that localTimeoutTimestamp is 0 or greater than the current timestamp, otherwise the query will always time out.
+ abortTransactionUnless(localTimeoutTimestamp === 0 || localTimeoutTimestamp > currentTimestamp())
+
+ // Generate a unique query identifier.
+ queryIdentifier = generateQueryIdentifier()
+
+ // Create a query request record.
+ query = CrossChainQuery{queryIdentifier,
+ path,
+ queryHeight,
+ localTimeoutHeight,
+ localTimeoutTimestamp,
+ clientId,
+ bounty}
+
+ // Store the query in the local, private store.
+ privateStore.set(queryPath(queryIdentifier), query)
+
+ queryCapability = newCapability(queryIdentifier)
+
+ // Log the query request.
+ emitLogEntry("sendQuery", query)
+
+ // Returns the query identifier.
+ return [queryIdentifier, queryCapability]
+}
+```
+
+- **Precondition**
+ - There exists a client with `clientId` identifier.
+- **Postcondition**
+ - The query request is stored in the `privateStore`.
+ - A `sendQuery` event is emitted.
+
+The `CrossChainQueryResponse` function is called when the Cross-chain Queries module at the querying chain receives a new query reply.
+We pass the address of the relayer that submitted the query result to the querying chain to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard).
+
+```typescript
+function CrossChainQueryResponse(
+ queryId: Identifier,
+ data: []byte
+ proof: CommitmentProof,
+ proofHeight: Height,
+ delayPeriodTime: uint64,
+ delayPeriodBlocks: uint64,
+ relayer: string
+ ) {
+
+ // Retrieve query state from the local, private store using the query's identifier.
+ query = privateStore.get(queryPath(queryIdentifier))
+ abortTransactionUnless(query !== null)
+
+ // Retrieve client state of the queried chain.
+ clientState = queryClientState(query.clientId)
+ abortTransactionUnless(client !== null)
+
+ // Check that the relier executed the query at the requested height at the queried chain.
+ abortTransactionUnless(query.queryHeight !== proofHeight)
+
+ // Check that localTimeoutHeight is 0 or greater than the current height.
+ abortTransactionUnless(query.localTimeoutHeight === 0 || query.localTimeoutHeight > getCurrentHeight())
+ // Check that localTimeoutTimestamp is 0 or greater than the current timestamp.
+ abortTransactionUnless(query.localTimeoutTimestamp === 0 || query.localTimeoutTimestamp > currentTimestamp())
+
+
+ // Verify query result using the local light client of the queried chain.
+ // If the response carries data, then verify that the data is indeed the value associated with query.path at query.queryHeight at the queried chain.
+ if (data !== null) {
+ abortTransactionUnless(verifyMembership(
+ clientState,
+ proofHeight,
+ delayPeriodTime,
+ delayPeriodBlocks,
+ proof,
+ query.path,
+ data
+ ))
+ result = SUCCESS
+ // If the response does not carry any data, verify that query.path does not exist at query.queryHeight at the queried chain.
+ } else {
+ abortTransactionUnless(verifyNonMembership(
+ clientState,
+ proofHeight,
+ delayPeriodTime,
+ delayPeriodBlocks,
+ proof,
+ query.path,
+ ))
+ result = FAILURE
+ }
+
+ // Delete the query from the local, private store.
+ privateStore.delete(queryPath(queryId))
+
+ // Create a query result record.
+ resultRecord = CrossChainQuery{queryIdentifier,
+ result,
+ data}
+
+ // Store the result in the local, private store.
+ privateStore.set(queryResultPath(queryIdentifier), resultRecord)
+
+}
+```
+
+- **Precondition**
+ - There exists a client with `clientId` identifier.
+ - There is a query request stored in the `privateStore` identified by `queryId`.
+- **Postcondition**
+ - The query request identified by `queryId` is deleted from the `privateStore`.
+ - The query result is stored in the `privateStore`.
+
+The `PruneCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it.
+
+```typescript
+function PruneCrossChainQueryResult(
+ queryId: Identifier,
+ queryCapability: CapabilityKey
+ ) {
+
+ // Retrieve the query result from the private store using the query's identifier.
+ resultRecord = privateStore.get(queryResultPath(queryIdentifier))
+ abortTransactionUnless(resultRecord !== null)
+
+ // Abort the transaction unless the caller has the right to clean the query result
+ abortTransactionUnless(authenticateCapability(queryId, queryCapability))
+
+ // Delete the query result from the local, private store.
+ privateStore.delete(queryResultPath(queryId))
+}
+```
+
+- **Precondition**
+ - There is a query result stored in the `privateStore` identified by `queryId`.
+ - The caller has the right to clean the query result
+- **Postcondition**
+ - The query result identified by `queryId` is deleted from the `privateStore`.
+
+#### Timeouts
+
+Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the querying chain after which a query is considered to have failed.
+
+There are several alternatives on how to handle timeouts. For instance, the relayer could submit timeout notifications as transactions to the querying chain. Since the relayer is untrusted, for each of these notifications, the Cross-chain Queries module of the querying chain MUST call the `checkQueryTimeout` to check if the query has indeed timed out. An alternative could be to make the Cross-chain Queries module responsible for checking if any query has timed out by iterating over the ongoing queries at the beginning of a block and calling `checkQueryTimeout`. In this case, ongoing queries should be stored indexed by `localTimeoutTimestamp` and `localTimeoutHeight` to allow iterating over them more efficiently. These are implementation details that this specification does not cover.
+
+Assume that the relayer is in charge of submitting timeout notifications as transactions. The `checkQueryTimeout` function would look as follows. Note that
+we pass the relayer address just as in `CrossChainQueryResponse` to allow for possible incentivization here as well.
+
+```typescript
+function checkQueryTimeout(
+ queryId: Identifier,
+ relayer: string
+){
+ // Retrieve the query state from the local, private store using the query's identifier.
+ query = privateStore.get(queryPath(queryIdentifier))
+ abortTransactionUnless(query !== null)
+
+ // Get the current height.
+ currentHeight = getCurrentHeight()
+
+ // Check that localTimeoutHeight or localTimeoutTimestamp has passed on the querying chain (locally)
+ abortTransactionUnless(
+ (query.localTimeoutHeight > 0 && query.localTimeoutHeight < getCurrentHeight()) ||
+ (query.localTimeoutTimestamp > 0 && query.localTimeoutTimestamp < currentTimestamp()))
+
+ // Delete the query from the local, private store if it has timed out
+ privateStore.delete(queryPath(queryId))
+
+ // Create a query result record.
+ resultRecord = CrossChainQuery{queryIdentifier,
+ TIMEOUT,
+ query.caller
+ null}
+
+ // Store the result in the local, private store.
+ privateStore.set(resultQueryPath(queryIdentifier), resultRecord)
+}
+```
+
+- **Precondition**
+ - There is a query request stored in the `privateStore` identified by `queryId`.
+- **Postcondition**
+ - If the query has indeed timed out, then
+ - the query request identified by `queryId` is deleted from the `privateStore`;
+ - the fact that the query has timed out is recorded in the `privateStore`.
+
+## History
+
+January 6, 2022 - First draft
+
+May 11, 2022 - Major revision
+
+June 14, 2022 - Adds pruning, localTimeoutTimestamp and adds relayer address for incentivization
+
+July 28, 2022 - Revision of the assumptions
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-100-atomic-swap/README.mdx b/ibc/next/spec/app/ics-100-atomic-swap/README.mdx
new file mode 100644
index 00000000..7ead581d
--- /dev/null
+++ b/ibc/next/spec/app/ics-100-atomic-swap/README.mdx
@@ -0,0 +1,660 @@
+---
+ics: 100
+title: Atomic Swap
+stage: draft
+category: IBC/APP
+requires: [25, 26]
+kind: instantiation
+authors:
+ - name: Ping Liang
+ email: ping@side.one
+ - name: Edward Gunawan
+ email: edward@s16.ventures
+created: 2022-07-27
+modified: 2022-10-07
+---
+
+
+
+This specification has been reviewed by the Spec Committee, but it is not responsible for maintaining it.
+
+
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for the atomic swap of fungible tokens over an IBC channel between two modules on separate chains.
+
+### Motivation
+
+Users may wish to exchange tokens without transferring tokens away from their native chain. ICS-100 enabled chains can facilitate atomic swaps between users and their tokens located on the different chains. This is useful for exchanges between specific users at specific prices, and opens opportunities for new application designs.
+
+For example, a token exchange would require only one transaction from a user, compared to multiple transactions when using ICS-20. Additionally, users can minimize trade slippage compared to using a liquidity pool, given there is a willing counter-party.
+
+### Definitions
+
+`Atomic Swap`: An exchange of tokens from separate chains without transferring tokens from one blockchain to another. The exchange either happens or it doesn't -- there is no other alternative.
+
+`Order`: An offer to exchange quantity X of token A for quantity Y of token B. Tokens offered are sent to an escrow account (owned by the module).
+
+`Maker`: A user that makes or initiates an order.
+
+`Taker`: The counterparty who takes or responds to an order.
+
+`Maker Chain`: The blockchain where a maker makes or initiates an order.
+
+`Taker Chain`: The blockchain where a taker takes or responds to an order.
+
+### Desired Properties
+
+- `Permissionless`: no need to whitelist connections, modules, or denominations.
+- `Guarantee of exchange`: no occurrence of a user receiving tokens without the equivalent promised exchange.
+- `Escrow enabled`: an account owned by the module will hold tokens and facilitate exchange.
+- `Refundable`: tokens are refunded by escrow when a timeout occurs, or when an order is cancelled.
+- `Order cancellation`: orders without takers can be cancelled.
+- `Basic orderbook`: a store of orders functioning as an orderbook system.
+- `Atomicity`: an exchange of one token for another where it is either a total success or a total failure.
+
+## Technical Specification
+
+### General Design
+
+
+
+
+
+A maker offers token A in exchange for token B by making an order. The order specifies the quantity and price of exchange, and sends the offered token A to the maker chain's escrow account. Any taker on a different chain with token B can accept the offer by taking the order. The taker sends the desired amount of token B to the taker chain's escrow account. The escrow account on each respective chain transfers the corresponding token amounts to each user's receiving address, without requiring the usual IBC transfer.
+
+An order without takers can be cancelled. This enables users to rectify mistakes, such as inputting an incorrect price or taker address. Upon cancellation escrowed tokens will be refunded.
+
+When making or taking an order, a timeout window is specified in the relayed data packet. A timeout will result in escrowed tokens refunded back. This timeout window is customizable.
+
+### Data Structures
+
+Only one packet data type is required: `AtomicSwapPacketData`, which specifies the swap message type, data (protobuf marshalled) and a memo field.
+
+```typescript
+enum SwapMessageType {
+ // Default zero value enumeration
+ TYPE_UNSPECIFIED = 0,
+ TYPE_MSG_MAKE_SWAP = 1,
+ TYPE_MSG_TAKE_SWAP = 2,
+ TYPE_MSG_CANCEL_SWAP = 3,
+}
+```
+
+```typescript
+// AtomicSwapPacketData is comprised of a swap message type, raw transaction and optional memo field.
+interface AtomicSwapPacketData {
+ type: SwapMessageType
+ data: bytes
+ memo: string
+}
+```
+
+```typescript
+type AtomicSwapPacketAcknowledgement = AtomicSwapPacketSuccess | AtomicSwapPacketError
+interface AtomicSwapPacketSuccess {
+ // This is binary 0x01 base64 encoded
+ result: "AQ=="
+}
+
+interface AtomicSwapPacketError {
+ error: string
+}
+```
+
+All `AtomicSwapPacketData` will be forwarded to the corresponding message handler to execute according to its type. There are 3 types:
+
+```typescript
+interface MakeSwapMsg {
+ // the port on which the packet will be sent, specified by the maker when the message is created
+ sourcePort: string
+ // the channel on which the packet will be sent, specified by the maker when the message is created
+ sourceChannel: string
+ // the tokens to be exchanged
+ sellToken: Coin
+ buyToken: Coin
+ // the maker's address
+ makerAddress: string
+ // the maker's address on the taker chain
+ makerReceivingAddress string
+ // if desiredTaker is specified,
+ // only the desiredTaker is allowed to take this order
+ // this is the address on the taker chain
+ desiredTaker: string
+ creationTimestamp: uint64
+ expirationTimestamp: uint64
+ timeoutHeight: Height
+ timeoutTimestamp: uint64
+}
+```
+
+```typescript
+interface TakeSwapMsg {
+ orderId: string
+ // the tokens to be sold
+ sellToken: Coin
+ // the taker's address
+ takerAddress: string
+ // the taker's address on the maker chain
+ takerReceivingAddress: string
+ creationTimestamp: uint64
+ timeoutHeight: Height
+ timeoutTimestamp: uint64
+}
+```
+
+```typescript
+interface CancelSwapMsg {
+ orderId: string
+ makerAddress: string
+ timeoutHeight: Height
+ timeoutTimestamp: uint64
+}
+```
+
+where `Coin` is an interface that consists of an amount and a denomination:
+
+```ts
+interface Coin {
+ amount: int64
+ denom: string
+}
+```
+
+Both the maker chain and taker chain maintain separate order store. Orders are saved in both maker chain and taker chain.
+
+```typescript
+// Status of order
+enum Status {
+ INITIAL = 0, // initialed on maker chain
+ SYNC = 1, // synced to the taker chain
+ CANCEL = 2, // canceled
+ COMPLETE = 3, // completed
+}
+
+interface Order {
+ id: string
+ maker: MakeSwapMsg
+ status: Status
+ // an IBC path, define channel and port on both Maker Chain and Taker Chain
+ path: string
+ taker: TakeSwapMsg
+ cancelTimestamp: uint64
+ completeTimestamp: uint64
+}
+
+function createOrder(msg: MakeSwapMsg, packet: channelType.Packet): Order {
+ const path = orderPath(packet)
+ return Order{
+ id : generateOrderId(packet),
+ status: Status.INITIAL,
+ path: path,
+ maker: msg,
+ }
+ }
+
+function orderPath(packet: channelType.Packet): string {
+ return `channel/${packet.sourceChannel}/port/${packet.sourcePort}/channel/${packet.destChannel}/port/${packet.destPort}/sequence/${packet.sequence}`
+
+}
+// Order id is a global unique string, since packet contains sourceChannel, SourcePort, distChannel, distPort, sequence and msg data
+function generateOrderId(packet: channelType.Packet) : string {
+ const bytes = protobuf.encode(packet)
+ return sha265(bytes)
+}
+
+function extractSourceChannelForTakerMsg(path: string) : string {
+ return path.split('/')[5]
+}
+function extractSourcePortForTakerMsg(path: string) : string{
+ return path.split('/')[7]
+}
+```
+
+### Life scope and control flow
+
+#### Making a swap
+
+1. A maker creates an order on the maker chain with specified parameters (see type `MakeSwap`). The maker's sell tokens are sent to the escrow address owned by the module. The order is saved on the maker chain.
+2. An `AtomicSwapPacketData` is relayed to the taker chain where in `onRecvPacket` the order is also saved on the taker chain.
+3. A packet is subsequently relayed back for acknowledgement. A packet timeout or a failure during `onAcknowledgePacket` will result in a refund of the escrowed tokens.
+
+#### Taking a swap
+
+1. A taker takes an order on the taker chain by triggering `TakeSwap`. The taker's sell tokens are sent to the escrow address owned by the module. An order cannot be taken if the current time is later than the `expirationTimestamp`.
+2. An `AtomicSwapPacketData` is relayed to the maker chain where in `onRecvPacket` the escrowed tokens are sent to the taker address on the maker chain.
+3. A packet is subsequently relayed back for acknowledgement. Upon acknowledgement escrowed tokens on the taker chain are sent to the maker address on the taker chain. A packet timeout or a failure during `onAcknowledgePacket` will result in a refund of the escrowed tokens.
+
+#### Cancelling a swap
+
+1. A maker cancels a previously created order. Expired orders can also be cancelled.
+2. An `AtomicSwapPacketData` is relayed to the taker chain where in `onRecvPacket` the order is cancelled on the taker chain. If the order is in the process of being taken (a packet with `TakeSwapMsg` is being relayed from the taker chain to the maker chain), the cancellation will be rejected.
+3. A packet is relayed back where upon acknowledgement the order on the maker chain is also cancelled. The refund only occurs if the taker chain confirmed the cancellation request.
+
+### Sub-protocols
+
+The sub-protocols described herein should be implemented in a "Fungible Token Swap" module with access to a bank module and to the IBC routing module.
+
+```ts
+function makeSwap(request: MakeSwapMsg, packet: channelTypes.Packet) {
+ const balance = bank.getBalances(request.makerAddress, request.sellToken.denom)
+ abortTransactionUnless(balance.amount >= request.sellToken.Amount)
+ // gets escrow address by source port and source channel
+ const escrowAddr = escrowAddress(request.sourcePort, request.sourceChannel)
+ // locks the sellToken to the escrow account
+ const err = bank.sendCoins(request.makerAddress, escrowAddr, request.sellToken.amount, request.sellToken.denom)
+ abortTransactionUnless(err === null)
+ // constructs the IBC data packet
+ const packet = {
+ type: SwapMessageType.TYPE_MSG_MAKE_SWAP,
+ data: protobuf.encode(request), // encode the request message to protobuf bytes
+ memo: ""
+ }
+ sendAtomicSwapPacket(packet, request.sourcePort, request.sourceChannel, request.timeoutHeight, request.timeoutTimestamp)
+
+ // creates and saves order on the maker chain.
+ const order = createOrder(request, packet)
+ // saves order to store
+ privateStore.set(order.id, order)
+}
+```
+
+```ts
+function takeSwap(request: TakeSwapMsg) {
+ const order = privateStore.get(request.orderId)
+ abortTransactionUnless(order !== null)
+ abortTransactionUnless(order.expirationTimestamp < currentTimestamp())
+ abortTransactionUnless(order.maker.buyToken.denom === request.sellToken.denom)
+ abortTransactionUnless(order.maker.buyToken.amount === request.sellToken.amount)
+ abortTransactionUnless(order.taker === null)
+ // if `desiredTaker` is set, only the desiredTaker can accept the order.
+ abortTransactionUnless(order.maker.desiredTaker !== null && order.maker.desiredTaker !== request.takerAddress)
+
+ const balance = bank.getBalances(request.takerAddress, request.sellToken.denom)
+ abortTransactionUnless(balance.amount >= request.sellToken.amount)
+
+ const sourceChannel = extractSourceChannelForTakerMsg(order.path)
+ const sourcePort = extractSourcePortForTakerMsg(order.path)
+
+ // gets the escrow address by source port and source channel
+ const escrowAddr = escrowAddress(sourcePort, sourceChannel)
+ // locks the sellToken to the escrow account
+ const err = bank.sendCoins(request.takerAddress, escrowAddr, request.sellToken.amount, request.sellToken.denom)
+ abortTransactionUnless(err === null)
+ // constructs the IBC data packet
+ const packet = {
+ type: SwapMessageType.TYPE_MSG_TAKE_SWAP,
+ data: protobuf.encode(request), // encode the request message to protobuf bytes.
+ memo: ""
+ }
+
+ sendAtomicSwapPacket(packet, sourcePort, sourceChannel, request.timeoutHeight, request.timeoutTimestamp)
+
+ // update order state
+ order.taker = request // mark that the order has been occupied
+ privateStore.set(order.orderId, order)
+}
+```
+
+```ts
+function cancelSwap(request: CancelSwapMsg) {
+ const order = privateStore.get(request.orderId)
+ // checks if the order exists
+ abortTransactionUnless(order !== null)
+ // make sure the sender is the maker of the order.
+ abortTransactionUnless(order.maker.makerAddress == request.makerAddress)
+ abortTransactionUnless(order.status == Status.SYNC || order.status == Status.INITIAL)
+
+ // constructs the IBC data packet
+ const packet = {
+ type: SwapMessageType.TYPE_MSG_CANCEL_SWAP,
+ data: protobuf.encode(request), // encode the request message to protobuf bytes.
+ memo: "",
+ }
+ // the request is sent to the taker chain, and the taker chain decides if the cancel order is accepted or not
+ // the cancellation can only be sent to the same chain as the make order.
+ sendAtomicSwapPacket(packet, order.maker.sourcePort, order.maker.sourceChannel request.timeoutHeight, request.timeoutTimestamp)
+}
+```
+
+#### Port & channel setup
+
+The fungible token swap module on a chain must always bind to a port with the id `atomicswap`.
+
+The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port and create an escrow address (owned by the module).
+
+```typescript
+function setup() {
+ capability = routingModule.bindPort("atomicswap", ModuleCallbacks{
+ onChanOpenInit,
+ onChanOpenTry,
+ onChanOpenAck,
+ onChanOpenConfirm,
+ onChanCloseInit,
+ onChanCloseConfirm,
+ onRecvPacket,
+ onTimeoutPacket,
+ onAcknowledgePacket,
+ onTimeoutPacketClose
+ })
+ claimCapability("port", capability)
+}
+```
+
+Once the setup function has been called, channels can be created via the IBC routing module.
+
+#### Channel lifecycle management
+
+An fungible token swap module will accept new channels from any module on another machine, if and only if:
+
+- The channel being created is unordered.
+- The version string is `ics100-1`.
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics100-1" or empty
+ // if empty, we return the default transfer version to core IBC
+ // as the version for this channel
+ abortTransactionUnless(version === "ics100-1" || version === "")
+
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress()
+
+ return "ics100-1", nil
+}
+```
+
+```typescript
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics100-1"
+ abortTransactionUnless(counterpartyVersion === "ics100-1")
+
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress()
+
+ return "ics100-1", nil
+}
+```
+
+```typescript
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string
+) {
+ // port has already been validated
+ // assert that counterparty selected version is "ics100-1"
+ abortTransactionUnless(counterpartyVersion === "ics100-1")
+}
+```
+
+```typescript
+function onChanOpenConfirm(portIdentifier: Identifier, channelIdentifier: Identifier) {
+ // accept channel confirmations, port has already been validated, version has already been validated
+}
+```
+
+```typescript
+function onChanCloseInit(portIdentifier: Identifier, channelIdentifier: Identifier) {
+ // always abort transaction
+ abortTransactionUnless(FALSE)
+}
+```
+
+```typescript
+function onChanCloseConfirm(portIdentifier: Identifier, channelIdentifier: Identifier) {
+ // no action necessary
+}
+```
+
+#### Packet relay
+
+`sendAtomicSwapPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.
+
+```typescript
+function sendAtomicSwapPacket(
+ swapPacket: AtomicSwapPacketData,
+ sourcePort: string,
+ sourceChannel: string,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64
+) {
+ // send packet using the interface defined in ICS4
+ handler.sendPacket(
+ getCapability("port"),
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ protobuf.marshal(swapPacket) // protobuf-marshalled bytes of packet data
+ )
+}
+```
+
+`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.
+
+```typescript
+function onRecvPacket(packet channeltypes.Packet) {
+ const swapPacket: AtomicSwapPacketData = packet.data
+
+ AtomicSwapPacketAcknowledgement ack = AtomicSwapPacketAcknowledgement{true, null}
+
+ switch swapPacket.type {
+ case TYPE_MSG_MAKE_SWAP:
+ const makeMsg = protobuf.decode(swapPacket.data)
+
+ // check if buyToken is a valid token on the taker chain, could be either native or ibc token
+ const supply = bank.getSupply(makeMsg.buyToken.denom)
+ if (supply <= 0) {
+ ack = AtomicSwapPacketAcknowledgement{false, "sell token does not exist on the taker chain"}
+ break;
+ }
+
+ // create and save order on the taker chain.
+ const order = createOrder(makeMsg, packet)
+ order.status = Status.SYNC
+ // saves order to store
+ const err = privateStore.set(order.orderId, order)
+ if (err != null) {
+ ack = AtomicSwapPacketAcknowledgement{false, "failed to save the order on taker chain"}
+ }
+ break;
+ case TYPE_MSG_TAKE_SWAP:
+ const takeMsg = protobuf.decode(swapPacket.data)
+ const order = privateStore.get(takeMsg.orderId)
+ abortTransactionUnless(order !== null)
+ abortTransactionUnless(order.status === Status.SYNC)
+ abortTransactionUnless(order.expiredTimestamp < currentTimestamp())
+ abortTransactionUnless(takeMsg.sellToken.denom === order.maker.buyToken.denom)
+ abortTransactionUnless(takeMsg.sellToken.amount === order.maker.buyToken.amount)
+ // if `desiredTaker` is set, only the desiredTaker can accept the order.
+ abortTransactionUnless(order.maker.desiredTaker !== null && order.maker.desiredTaker !== takeMsg.takerAddress)
+
+ const escrowAddr = escrowAddress(order.maker.sourcePort, order.maker.sourceChannel)
+ // send maker.sellToken to taker's receiving address
+ const err = bank.sendCoins(escrowAddr, takeMsg.takerReceivingAddress, order.maker.sellToken.amount, order.maker.sellToken.denom)
+ if (err != null) {
+ ack = AtomicSwapPacketAcknowledgement{false, "transfer coins failed"}
+ break;
+ }
+
+ // update status of order
+ order.status = Status.COMPLETE
+ order.taker = takeMsg
+ order.completeTimestamp = takeMsg.creationTimestamp
+ privateStore.set(takeMsg.orderId, order)
+ break;
+ case TYPE_MSG_CANCEL_SWAP:
+ const cancelMsg = protobuf.decode(swapPacket.data)
+ const order = privateStore.get(cancelMsg.orderId)
+ abortTransactionUnless(order !== null)
+ abortTransactionUnless(order.status === Status.SYNC || order.status == Status.INITIAL)
+ abortTransactionUnless(order.taker !== null) // the maker order has not been occupied
+
+ // update status of order
+ order.status = Status.CANCEL
+ order.cancelTimestamp = cancelMsg.creationTimestamp
+ const err = privateStore.set(cancelMsg.orderId, order)
+ if (err != null) {
+ ack = AtomicSwapPacketAcknowledgement{false, "failed to cancel order on taker chain"}
+ }
+ break;
+ default:
+ ack = AtomicSwapPacketAcknowledgement{false, "unknown data packet"}
+ }
+
+ return ack
+}
+```
+
+`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.
+
+```typescript
+function onAcknowledgePacket(
+ packet: channeltypes.Packet
+ acknowledgement: bytes) {
+ // ack is failed
+ if (!acknowledgement.success) {
+ refundTokens(packet)
+ } else {
+
+ const swapPacket: AtomicSwapPacketData = protobuf.decode(packet.data)
+ const escrowAddr = escrowAddress(packet.sourcePort, packet.sourceChannel)
+
+ switch swapPacket.type {
+ case TYPE_MSG_MAKE_SWAP:
+ const makeMsg = protobuf.decode(swapPacket.data)
+
+ // update order status on the maker chain.
+ const order = privateStore.get(generateOrderId(packet))
+ order.status = Status.SYNC
+ // save order to store
+ privateStore.set(order.id, order)
+ break;
+ case TYPE_MSG_TAKE_SWAP:
+ const takeMsg = protobuf.decode(swapPacket.data)
+
+ // update order status on the taker chain.
+ const order = privateStore.get(takeMsg.orderId)
+
+ // send tokens to maker
+ bank.sendCoins(escrowAddr, order.maker.makerReceivingAddress, takeMsg.sellToken.amount, takeMsg.sellToken.denom)
+
+ order.status = Status.COMPLETE
+ order.taker = takeMsg
+ order.completeTimestamp = takeMsg.creationTimestamp
+ privateStore.set(order.id, order)
+
+ break;
+ case TYPE_MSG_CANCEL_SWAP:
+ const cancelMsg = protobuf.decode(swapPacket.data)
+
+ // update order status on the maker chain.
+ const order = privateStore.get( cancelMsg.orderId )
+
+ // send tokens back to maker
+ bank.sendCoins(escrowAddr, order.maker.makerAddress, order.maker.sellToken.amount, order.maker.sellToken.denom)
+
+ // update state on maker chain
+ order.status = Status.CANCEL
+ order.cancelTimestamp = cancelMsg.creationTimestamp
+ privateStore.set(order.id, order)
+
+ break;
+ default:
+ throw new Error("ErrUnknownDataPacket")
+ }
+ }
+}
+```
+
+`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed-out (such that the tokens will be refunded).
+
+```typescript
+function onTimeoutPacket(packet: Packet) {
+ // the packet timed-out, so refund the tokens
+ refundTokens(packet)
+}
+```
+
+`refundTokens` is called by both `onAcknowledgePacket` on failure, and `onTimeoutPacket`, to refund escrowed tokens to the original owner.
+
+```typescript
+function refundTokens(packet: Packet) {
+ const swapPacket: AtomicSwapPacketData = protobuf.decode(packet.data)
+ const escrowAddr = escrowAddress(packet.sourcePort, packet.sourceChannel)
+
+ // send tokens from module to message sender
+ switch swapPacket.type {
+ case TYPE_MSG_MAKE_SWAP:
+ const msg = protobuf.decode(swapPacket.data)
+ bank.sendCoins(escrowAddr, msg.makerAddress, msg.sellToken.amount, msg.sellToken.denom)
+ const orderId = generateOrderId(packet)
+ const order = privateStore.get(order.orderId)
+ order.status = Status.CANCEL
+ privateStore.set(order.orderId, order)
+ break;
+ case TYPE_MSG_TAKE_SWAP:
+ const msg = protobuf.decode(swapPacket.data)
+ bank.sendCoins(escrowAddr, msg.takerAddress, msg.sellToken.amount, msg.sellToken.denom)
+ const order = privateStore.get(msg.orderId)
+ order.taker = null // release the occupation
+ privateStore.set(order.orderId, order)
+ break;
+ case TYPE_MSG_CANCEL_SWAP:
+ // do nothing, only send tokens back when cancel msg is acknowledged.
+ }
+}
+```
+
+```typescript
+function onTimeoutPacketClose(packet: AtomicSwapPacketData) {
+ // can't happen, only unordered channels allowed
+}
+```
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+This initial standard uses version "ics100-1" in the channel handshake.
+
+A future version of this standard could use a different version in the channel handshake,
+and safely alter the packet data format & packet handler semantics.
+
+## Example Implementations
+
+- Implementation of ICS 100 in Go can be found in [ibcswap repository](https://github.com/ibcswap/ibcswap).
+
+## History
+
+Aug 15, 2022 - Draft written
+
+Oct 6, 2022 - Draft revised
+
+Nov 11, 2022 - Draft revised
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/app/ics-100-atomic-swap/ibcswap.png b/ibc/next/spec/app/ics-100-atomic-swap/ibcswap.png
new file mode 100644
index 00000000..3e659e22
Binary files /dev/null and b/ibc/next/spec/app/ics-100-atomic-swap/ibcswap.png differ
diff --git a/ibc/next/spec/app/ics-721-nft-transfer/README.mdx b/ibc/next/spec/app/ics-721-nft-transfer/README.mdx
new file mode 100644
index 00000000..601a306f
--- /dev/null
+++ b/ibc/next/spec/app/ics-721-nft-transfer/README.mdx
@@ -0,0 +1,490 @@
+---
+ics: 721
+title: Non-Fungible Token Transfer
+stage: draft
+category: IBC/APP
+requires: [25, 26]
+kind: instantiation
+authors:
+ - name: Haifeng Xi
+ email: haifeng@bianjie.ai
+created: 2021-11-10
+modified: 2022-12-15
+---
+
+> This standard document follows the same design principles of [ICS 20](../ics-020-fungible-token-transfer) and inherits most of its content therefrom, while replacing `bank` module based asset tracking logic with that of the `nft` module.
+
+## Synopsis
+
+This standard document specifies packet data structure, state machine handling logic, and encoding details for the transfer of non-fungible tokens over an IBC channel between two modules on separate chains. In this document, `class`, `collection` and `contract` are used interchangeably. The state machine logic presented allows for safe multi-chain `classId` handling with permissionless channel opening. This logic constitutes a *non-fungible token transfer bridge module*, interfacing between the IBC routing module and an existing asset tracking module on the host state machine, which could be either a Cosmos-style native module or a smart contract running in a virtual machine.
+
+### Motivation
+
+Users of a set of chains connected over the IBC protocol might wish to utilize a non-fungible token on a chain other than the chain where the token was originally issued -- perhaps to make use of additional features such as exchange, royalty payment or privacy protection. This application-layer standard describes a protocol for transferring non-fungible tokens between chains connected with IBC which preserves asset non-fungibility, preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning.
+
+### Definitions
+
+The IBC handler interface & IBC routing module interface are as defined in [ICS 25](../../core/ics-025-handler-interface) and [ICS 26](../../core/ics-026-routing-module), respectively.
+
+### Desired Properties
+
+- Preservation of non-fungibility (i.e., only one instance of any token is *live* across all the IBC-connected blockchains).
+- Permissionless token transfers, no need to whitelist connections, modules, or `classId`s.
+- Symmetric (all chains implement the same logic, no in-protocol differentiation of hubs & zones).
+- Fault containment: prevents Byzantine-creation of tokens originating on chain `A`, as a result of chain `B`'s Byzantine behavior.
+
+## Technical Specification
+
+### Data Structures
+
+Only one packet data type is required: `NonFungibleTokenPacketData`, which specifies the class id, class uri, class data, token id array, token uri array, token data array, sending address, and receiving address.
+
+```typescript
+interface NonFungibleTokenPacketData {
+ classId: string
+ classUri: string
+ classData: string
+ tokenIds: string[]
+ tokenUris: string[]
+ tokenData: string[]
+ sender: string
+ receiver: string
+ memo: string
+}
+```
+
+`classId` is a required field that MUST never be empty, it uniquely identifies the class/collection/contract which the tokens being transferred belong to in the sending chain. In the case of an ERC-1155 compliant smart contract, for example, this could be a string representation of the top 128 bits of the token ID.
+
+`classUri` is an optional field which, if present, MUST be non-empty and refer to an off-chain resource that is typically a JSON file containing the [class metadata](https://docs.opensea.io/docs/contract-level-metadata); this could be extremely beneficial for cross-chain interoperability with NFT marketplaces like OpenSea.
+
+`classData` is an optional field which, if present, MUST be non-empty and contain on-chain class metadata such as royalty related parameters.
+
+`tokenIds` array is a required field that MUST have a size greater than zero and hold non-empty entries that uniquely identify tokens (of the given class) that are being transferred. In the case of an ERC-1155 compliant smart contract, for example, a `tokenId` could be a string representation of the bottom 128 bits of the token ID.
+
+`tokenUris` array is an optional field which, if present, MUST have the same size as `tokenIds` and hold non-empty entries each of which refers to an off-chain resource that is typically an immutable JSON file containing metadata associated with the token identified by the corresponding `tokenIds` entry.
+
+`tokenData` array is an optional field which, if present, MUST have the same size as `tokenIds` and hold non-empty entries each of which contains on-chain application data associated with the token identified by the corresponding `tokenIds` entry.
+
+Both `tokenData` entries and `classData` MUST be Base64 encoded strings which SHOULD have the following JSON structure:
+
+```json
+{
+ "key1" : { "value":"...", "mime":"..." },
+ "key2" : { "value":"...", "mime":"..." },
+ ...
+}
+```
+
+`mime` is an optional property that specifies the media type of the corresponding key-value. If a key-value is of the default type of string, then `mime` can be omitted. Otherwise, `mime` MUST be non-empty and have a value that comes from this [list](https://www.iana.org/assignments/media-types/media-types.xhtml).
+
+Chain applications are advised to namespace the keys; to achieve maximum interoperability across applications, standardization of these namespaces is desired but out of the scope.
+
+An example of `classData` content (raw JSON before being Base64 encoded) is shown below:
+
+```json
+{
+ "opensea:name" : { "value":"Crypto Creatures" },
+ "opensea:image" : { "value":"...(Base64 encoded media binary)", "mime":"image/png" },
+ "opensea:seller_fee_basis_points" : { "value":"100" }
+}
+```
+
+The optional `memo` field is not used within the transfer, however, it may be used either for external off-chain users (i.e. exchanges) or for middleware wrapping transfer that can parse and execute custom logic on the basis of the passed-in memo. If the memo is intended to be parsed and interpreted by higher-level middleware, then these middlewares are advised to namespace their additions to the memo string so that they do not overwrite each other. Chains should ensure that there is some length limit on the entire packet data to ensure that the packet does not become a DOS vector. However, these do not need to be protocol-defined limits. If the receiver cannot accept a packet because of length limitations, this will lead to a timeout on the sender side.
+
+As tokens are sent across chains using the ICS-721 protocol, they begin to accrue a record of channels across which they have been transferred. This record information is encoded into the `classId` field.
+
+An ICS-721 token class is represented in the form `{ics721Port}/{ics721Channel}/{classId}`, where `ics721Port` and `ics721Channel` identify the channel on the current chain from which the tokens arrived. If `{classId}` contains `/`, then it must also be in the ICS-721 form which indicates that the tokens have a multi-hop record. Note that this requires that the `/` (slash character) is prohibited in non-IBC token `classId`s.
+
+A sending chain may be acting as a source or sink zone. When a chain is sending tokens across a port and channel which are not equal to the last prefixed port and channel pair, it is acting as a source zone. When tokens are sent from a source zone, the destination port and channel will be prefixed onto the `classId` (once the tokens are received) adding another hop to the tokens record. When a chain is sending tokens across a port and channel which are equal to the last prefixed port and channel pair, it is acting as a sink zone. When tokens are sent from a sink zone, the last prefixed port and channel pair on the `classId` is removed (once the tokens are received), undoing the last hop in the tokens record.
+
+For example, assume these steps of transfer occur:
+
+A -> B -> C -> A -> C -> B -> A
+
+1. A(p1,c1) -> (p2,c2)B : A is source zone. `classId` in B: 'p2/c2/nftClass'
+2. B(p3,c3) -> (p4,c4)C : B is source zone. `classId` in C: 'p4/c4/p2/c2/nftClass'
+3. C(p5,c5) -> (p6,c6)A : C is source zone. `classId` in A: 'p6/c6/p4/c4/p2/c2/nftClass'
+4. A(p6,c6) -> (p5,c5)C : A is sink zone. `classId` in C: 'p4/c4/p2/c2/nftClass'
+5. C(p4,c4) -> (p3,c3)B : C is sink zone. `classId` in B: 'p2/c2/nftClass'
+6. B(p2,c2) -> (p1,c1)A : B is sink zone. `classId` in A: 'nftClass'
+
+The acknowledgement data type describes whether the transfer succeeded or failed, and the reason for failure (if any).
+
+```typescript
+type NonFungibleTokenPacketAcknowledgement =
+ | NonFungibleTokenPacketSuccess
+ | NonFungibleTokenPacketError
+
+interface NonFungibleTokenPacketSuccess {
+ // This is binary 0x01 base64 encoded
+ success: "AQ=="
+}
+
+interface NonFungibleTokenPacketError {
+ error: string
+}
+```
+
+Note that both the `NonFungibleTokenPacketData` as well as `NonFungibleTokenPacketAcknowledgement` must be JSON-encoded (not Protobuf encoded) when serialized into packet data.
+
+The non-fungible token transfer bridge module maintains a separate escrow address for each NFT channel.
+
+```typescript
+interface ModuleState {
+ channelEscrowAddresses: Map
+}
+```
+
+### Sub-protocols
+
+The sub-protocols described herein should be implemented in a "non-fungible token transfer bridge" module with access to the NFT asset tracking module and the IBC routing module.
+
+The NFT asset tracking module should implement the following functions:
+
+```typescript
+function CreateOrUpdateClass(classId: string, classUri: string, classData: string) {
+ // creates a new NFT Class identified by classId
+ // if classId already exists, app logic may choose to update class metadata accordingly
+}
+```
+
+```typescript
+function Mint(classId: string, tokenId: string, tokenUri: string, tokenData: string, receiver: string) {
+ // creates a new NFT identified by
+ // receiver becomes owner of the newly minted NFT
+}
+```
+
+```typescript
+function Transfer(classId: string, tokenId: string, receiver: string, tokenData: string) {
+ // transfers the NFT identified by to receiver
+ // receiver becomes new owner of the NFT
+ // if tokenData is not empty, app logic may choose to update token data accordingly
+}
+```
+
+```typescript
+function Burn(classId: string, tokenId: string) {
+ // destroys the NFT identified by
+}
+```
+
+```typescript
+function GetOwner(classId: string, tokenId: string) {
+ // returns current owner of the NFT identified by
+}
+```
+
+```typescript
+function GetNFT(classId: string, tokenId: string) {
+ // returns NFT identified by
+}
+```
+
+```typescript
+function GetClass(classId: string) {
+ // returns NFT Class identified by classId
+}
+```
+
+#### Port & channel setup
+
+The `setup` function must be called exactly once when the module is created (perhaps when the blockchain itself is initialised) to bind to the appropriate port (owned by the module).
+
+```typescript
+function setup() {
+ capability = routingModule.bindPort("nft", ModuleCallbacks{
+ onChanOpenInit,
+ onChanOpenTry,
+ onChanOpenAck,
+ onChanOpenConfirm,
+ onChanCloseInit,
+ onChanCloseConfirm,
+ onRecvPacket,
+ onTimeoutPacket,
+ onAcknowledgePacket,
+ onTimeoutPacketClose
+ })
+ claimCapability("port", capability)
+}
+```
+
+Once the `setup` function has been called, channels can be created through the IBC routing module between instances of the non-fungible token transfer module on separate chains.
+
+This specification defines packet handling semantics only, and defines them in such a fashion that the module itself doesn't need to worry about what connections or channels might or might not exist at any point in time.
+
+#### Routing module callbacks
+
+##### Channel lifecycle management
+
+Both machines `A` and `B` accept new channels from any module on another machine, if and only if:
+
+- The channel being created is unordered.
+- The version string is `ics721-1`.
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: Identifier[],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string): (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics721-1"
+ // or relayer passed in empty version
+ abortTransactionUnless(version === "ics721-1" || version === "")
+ return "ics721-1", nil
+}
+```
+
+```typescript
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: Identifier[],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string): (version: string, err: Error) {
+ // only unordered channels allowed
+ abortTransactionUnless(order === UNORDERED)
+ // assert that version is "ics721-1"
+ abortTransactionUnless(counterpartyVersion === "ics721-1")
+ return "ics721-1", nil
+}
+```
+
+```typescript
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string
+) {
+ // port has already been validated
+ // assert that version is "ics721-1"
+ abortTransactionUnless(counterpartyVersion === "ics721-1")
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress()
+}
+```
+
+```typescript
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+) {
+ // accept channel confirmations, port has already been validated, version has already been validated
+ // allocate an escrow address
+ channelEscrowAddresses[channelIdentifier] = newAddress()
+}
+```
+
+```typescript
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+) {
+ // abort and return error to prevent channel closing by user
+ abortTransactionUnless(FALSE)
+}
+```
+
+```typescript
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+) {
+ // no action necessary
+}
+```
+
+##### Packet relay
+
+- When a non-fungible token is sent away from its source, the bridge module escrows the token on the sending chain and mints a corresponding voucher on the receiving chain.
+- When a non-fungible token is sent back toward its source, the bridge module burns the token on the sending chain and unescrows the corresponding locked token on the receiving chain.
+- When a packet times out, tokens represented in the packet are either unescrowed or minted back to the sender appropriately -- depending on whether the tokens are being moved away from or back toward their source.
+- Acknowledgement data is used to handle failures, such as invalid destination accounts. Returning an acknowledgement of failure is preferable to aborting the transaction since it more easily enables the sending chain to take appropriate action based on the nature of the failure.
+
+`createOutgoingPacket` must be called by a transaction handler in the module which performs appropriate signature checks, specific to the account owner on the host state machine.
+
+```typescript
+function createOutgoingPacket(
+ classId: string,
+ tokenIds: string[],
+ sender: string,
+ receiver: string,
+ destPort: string,
+ destChannel: string,
+ sourcePort: string,
+ sourceChannel: string,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64): uint64 {
+ prefix = sourcePort + '/' + sourceChannel
+ // we are source chain if classId is not prefixed with sourcePort and sourceChannel
+ source = classId.slice(0, len(prefix)) !== prefix
+ tokenUris = []
+ tokenData = []
+ for (let tokenId in tokenIds) {
+ // ensure that sender is token owner
+ abortTransactionUnless(sender === nft.GetOwner(classId, tokenId))
+ if source { // we are source chain, escrow token
+ nft.Transfer(classId, tokenId, channelEscrowAddresses[sourceChannel], null)
+ } else { // we are sink chain, burn voucher
+ nft.Burn(classId, tokenId)
+ }
+ token = nft.GetNFT(classId, tokenId)
+ tokenUris.push(token.GetUri())
+ tokenData.push(token.GetData())
+ }
+ NonFungibleTokenPacketData data = NonFungibleTokenPacketData{
+ classId,
+ nft.GetClass(classId).GetUri(),
+ nft.GetClass(classId).GetData(),
+ tokenIds,
+ tokenUris,
+ tokenData,
+ sender,
+ receive
+ }
+ sequence = Handler.sendPacket(
+ getCapability("port"),
+ sourcePort,
+ sourceChannel,
+ timeoutHeight,
+ timeoutTimestamp,
+ protobuf.marshal(data) // protobuf-marshalled bytes of packet data
+ )
+ return sequence
+}
+```
+
+`onRecvPacket` is called by the routing module when a packet addressed to this module has been received.
+
+```typescript
+function onRecvPacket(packet: Packet) {
+ NonFungibleTokenPacketData data = packet.data
+ // construct default acknowledgement of success
+ NonFungibleTokenPacketAcknowledgement ack = NonFungibleTokenPacketAcknowledgement{true, null}
+ err = ProcessReceivedPacketData(data)
+ if (err !== null) {
+ ack = NonFungibleTokenPacketAcknowledgement{false, err.Error()}
+ }
+ return ack
+}
+
+function ProcessReceivedPacketData(data: NonFungibleTokenPacketData) {
+ prefix = data.sourcePort + '/' + data.sourceChannel
+ // we are source chain if classId is prefixed with packet's sourcePort and sourceChannel
+ source = data.classId.slice(0, len(prefix)) === prefix
+ for (var i in data.tokenIds) {
+ if source { // we are source chain, un-escrow token to receiver
+ nft.Transfer(data.classId.slice(len(prefix)), data.tokenIds[i], data.receiver, data.tokenData[i])
+ } else { // we are sink chain, mint voucher to receiver
+ prefixedClassId = data.destPort + '/' + data.destChannel + '/' + data.classId
+ nft.CreateOrUpdateClass(prefixedClassId, data.classUri, data.classData)
+ nft.Mint(prefixedClassId, data.tokenIds[i], data.tokenUris[i], data.tokenData[i], data.receiver)
+ }
+ }
+}
+```
+
+`onAcknowledgePacket` is called by the routing module when a packet sent by this module has been acknowledged.
+
+```typescript
+function onAcknowledgePacket(packet: Packet, acknowledgement: bytes) {
+ // if the transfer failed, refund the tokens
+ if (!acknowledgement.success) refundToken(packet)
+}
+```
+
+`onTimeoutPacket` is called by the routing module when a packet sent by this module has timed out (such that it will not be received on the destination chain).
+
+```typescript
+function onTimeoutPacket(packet: Packet) {
+ // the packet timed-out, so refund the tokens
+ refundToken(packet)
+}
+```
+
+`refundToken` is called by both `onAcknowledgePacket`, on failure, and `onTimeoutPacket`, to refund escrowed token to the original sender.
+
+```typescript
+function refundToken(packet: Packet) {
+ NonFungibleTokenPacketData data = packet.data
+ prefix = data.sourcePort + '/' + data.sourceChannel
+ // we are the source if the classId is not prefixed with the packet's sourcePort and sourceChannel
+ source = data.classId.slice(0, len(prefix)) !== prefix
+ for (var i in data.tokenIds) {
+ if source { // we are source chain, un-escrow token back to sender
+ nft.Transfer(data.classId, data.tokenIds[i], data.sender, null)
+ } else { // we are sink chain, mint voucher back to sender
+ nft.Mint(data.classId, data.tokenIds[i], data.tokenUris[i], data.tokenData[i], data.sender)
+ }
+ }
+}
+```
+
+```typescript
+function onTimeoutPacketClose(packet: Packet) {
+ // can't happen, only unordered channels allowed
+}
+```
+
+#### Reasoning
+
+##### Correctness
+
+This implementation preserves token non-fungibility and redeemability.
+
+- Non-fungibility: Only one instance of any token is *live* across all the IBC-connected blockchains.
+- Redeemability: If tokens have been sent to the counterparty chain, they can be redeemed back in the same `classId` & `tokenId` on the source chain.
+
+#### Optional addenda
+
+- Each chain, locally, could elect to keep a lookup table to use short, user-friendly local `classId`s in state which are translated to and from the longer `classId`s when sending and receiving packets.
+- Additional restrictions may be imposed on which other machines may be connected to & which channels may be established.
+
+## Further Discussion
+
+Extended and complex use cases such as royalties, marketplaces or permissioned transfers can be supported on top of this specification. Solutions could be modules, hooks, [IBC middleware](../ics-030-middleware) and so on. Designing a guideline for this is out of the scope.
+
+It is assumed that application logic in host state machines will be responsible for metadata immutability of IBC tokens minted according to this specification. For any IBC token, NFT applications are strongly advised to check upstream blockchains (all the way back to the source) to ensure its metadata has not been modified along the way. If it is decided, sometime in the future, to accommodate NFT metadata mutability over IBC, we will update this specification or create an entirely new specification -- by using advanced DID features perhaps.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+This initial standard uses version "ics721-1" in the channel handshake.
+
+A future version of this standard could use a different version in the channel handshake, and safely alter the packet data format & packet handler semantics.
+
+## Example Implementations
+
+- Implementation of ICS 721 in Go can be found in [bianjieai/nft-transfer repository](https://github.com/bianjieai/nft-transfer).
+
+## History
+
+| Date | Description |
+| ------------ | -------------------------------------------------------- |
+| Nov 10, 2021 | Initial draft - adapted from ICS 20 spec |
+| Nov 17, 2021 | Revised to better accommodate smart contracts |
+| Nov 17, 2021 | Renamed from ICS 21 to ICS 721 |
+| Nov 18, 2021 | Revised to allow for multiple tokens in one packet |
+| Feb 10, 2022 | Revised to incorporate feedbacks from IBC team |
+| Mar 03, 2022 | Revised to make TRY callback consistent with PR#629 |
+| Mar 11, 2022 | Added example to illustrate the prefix concept |
+| Mar 30, 2022 | Added NFT module definition and fixed pseudo-code errors |
+| May 18, 2022 | Added paragraph about NFT metadata mutability |
+| Nov 08, 2022 | Added `tokenData` to PacketData |
+| Dec 14, 2022 | Added `classData` and `memo` to PacketData |
+| Dec 15, 2022 | Tightened spec on `classData` and `tokenData` |
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/client/Rollup-Integration-guide.mdx b/ibc/next/spec/client/Rollup-Integration-guide.mdx
new file mode 100644
index 00000000..eca5f585
--- /dev/null
+++ b/ibc/next/spec/client/Rollup-Integration-guide.mdx
@@ -0,0 +1,240 @@
+---
+title: "Rollup Integration Guide"
+---
+
+## Context
+
+The following is a guide for rollup frameworks seeking to integrate with IBC. A rollup is a decentralized application that relies on a third-party blockchain for data availability (DA) and optionally for settlement. The rollup consensus mechanism differs from sovereign blockchains in important ways. The consensus on the blocks and ordering of the rollup is defined by the order in which they are posted onto a third party ledger, the DA layer. Since this third party ledger is not itself executing transactions and constructing the rollup app state, rollups may additionally have a settlement mechanism. There are two types of rollup architectures: optimistic and Zero Knowledge (ZK). ZK rollups submit a proof that the reported app hash is correctly constructed from the included transactions in the block, thus a rollup block and header can be trusted as legitimate as soon as it is finalized on the DA layer. An optimistic rollup on the other hand, relies on third party watchers, that can post a proof to a settlement layer that the rollup did not post the correct app hash from the posted transactions. This requires the settlement layer to be able to execute the rollup state machine. The DA layer and settlement layer **may** be different blockchains or the same.
+
+This guide is not intended to be a formal specification or Interchain Standard. As the architectures for rollups and their underlying data availability and settlement layers differ vastly: from ZK rollups to optimistic rollups with separate data availability and settlement layers to sovereign rollups; it is impossible to write a fully specified client to encompass all these cases. Thus this guide is intended to highlight the IBC client functions that are most affected by rollup specific features and explain what must be done in each one to take into account the unique properties of rollups. Rollup light client developers should use this document as a starting point when designing their light clients to ensure they are taking into account rollup-specific logic in the appropriate places.
+
+## Definitions
+
+**Execution Layer or Rollup**: This is the rollup blockchain itself. It executes the rollup application and derives its consensus and security from the underlying layers (e.g. DA layer). The rollup client is the light client that tracks the rollup blockchain.
+
+**Sequencer**: This is the actor(s) that collects user transaction and creates new rollup blocks. The sequencer must post these blocks to the data availability layer. Since the rollup's security is backed by the data availability and settlement layers, the sequencer does not need to be as decentralized as a sovereign validator set, it may even be a single operator. Some rollup architectures may even be "sequencerless", in this case, any actor may post new blocks to the data availability layer.
+
+**Data Availability Layer (DA layer)**: This is the ledger on which the rollup block producers must post their blocks. Any rollup user can thus download the rollup blockchain from the DA layer. Thus the Data Availability layer provides a guarantee of the availability of the rollup blocks and the included transactions. Since the Data Availability layer is a blockchain and thus has a definite ordering, the ordering of rollup blocks can be derived from their ordering on the data-availability layer. Thus, the rollup derives its consensus (i.e. the agreed upon ordering of included transactions) from the data availability layer. The DA client is the light client that tracks the data availability blockchain.
+
+**Settlement Layer**: The settlement layer is where disputes on the correctness of the posted rollup state is resolved. In addition to the included transactions, the rollup block producer must also post the state hash that results from applying the newly included transactions to the previous rollup state. If the rollup block producer posts an incorrect app hash for the posted block, any observer may submit a fraud proof to the settlement layer to dispute the incorrect app hash. At this point, the settlement layer must verify the fraud proof; often through a fraud proving game that requires the block producer and fraud submitter to narrow down on a disputed execution result before the settlement layer can execute the relevant logic to determine which party is honest. If the fraud is valid, the settlement layer must mark the fraudulent block as invalid. This block and any subsequent blocks built on top of it are invalidated and removed from the blockchain history of the rollup. The settlement layer is OPTIONAL as some rollup architectures do not involve settlement. For example, Celestia rollups are "sovereign rollups" and thus full nodes and the rollup p2p network itself is responsible for executing blocks and propagating fraud proofs. Also, the settlement layer MAY be the same ledger as the DA layer OR it may be completely different ledgers. The settlement client is the light client that tracks the settlement layer blockchain.
+
+**Sovereign Rollup:** Sovereign rollups post their blocks to a data availability layer, but do not rely on any other blockchain for correctness (ie settlement). Thus rollup nodes derive consensus and ordering from the data availability layer, but must execute the transactions themselves to verify correctness or obtain fraud proofs from the rollup p2p network.
+
+**Optimistic Rollup**: Optimistic rollups post their blocks to a data availability layer and rely on a settlement layer that can adjudicate fraud proofs submitted by rollup observers. Thus, rollup blocks are accepted "optimistically" before correctness can be guaranteed but they are only considered safe and finalized once a fraud window time period has passed without any successful challenge being submitted to the settlement layer.
+
+**ZK Rollup**: A ZK rollup has a Zero-Knowledge circuit that represents its state machine. Thus, a rollup block producer can submit a ZK-SNARK proof that the submitted app hash is indeed the correct result of applying the included transactions in the block. Thus, there is no need for a settlement layer or a fraud window. The block can be trusted and finalized as soon as the ZK proof is verified.
+
+### `verifyClientMessage`
+
+In order to verify a new header for the rollup, the rollup client must also be able to verify the header's (and associated block's) inclusion in the DA layer. Thus, the rollup client's update logic **must** have the ability to invoke verification of the associated DA client. After verifying the rollups own consensus mechanism (which itself may be non-existent for some rollup architectures), it verifies the header and blockdata in the data availability layer. Simply proving inclusion is not enough however, we must ensure that the data we are proving is valid; i.e. the data is not simply included but is included in the way that is expected by the rollup architecture. In the example below, we check that the blockdata hashes to the `txHash` in the header.
+
+ZK rollups can verify correctness of the header upon submission since the rollup client can embed a proving circuit that can verify a ZK proof from the relayer that the submitted header is correct. Optimistic rollups on the other hand cannot immediately trust a header upon submission, as the header may later be proved fraudulent. Thus, the header can be stored but must wait for the fraud period to elapse without any successful challenges to the correctness of the header before it is finalized and used for proof verification.
+
+```typescript
+function verifyClientMessage(clientMessage: ClientMessage) {
+ switch typeof(clientMessage) {
+ case Header:
+ verifyHeader(clientMessage)
+ case Misbehaviour:
+ // this is completely rollup specific so it is left unspecified here
+ // misbehaviour verification specification for rollups
+ // is instead described completely in checkForMisbehaviour
+ }
+}
+
+function verifyHeader(clientMessage: ClientMessage) {
+ clientState = provableStore.get("clients/{clientMessage.clientId}/clientState")
+ header = Header(clientMessage)
+
+ // note: unmarshalling logic omitted
+ // verify the header against the rollups own consensus mechanism if it exists
+ // e.g. verify sequencer signature
+ verifySignatures(header, clientSequencers)
+
+ // we must assert that the block data is correctly associated with the header
+ // this is specific to the rollup header and block architecture
+ // the following is merely an example of what might be verified
+ assert(hash(header.blockData) === header.txHash)
+
+ // In addition to the rollups own consensus mechanism verification,
+ // we must ensure that the header and associated block data is stored in the DA layer.
+ // The expected path, the header and data stored are
+ // rollup-specific so it is left as an unspecified function
+ // in this document. Though the path should reference a unique
+ // namespace for the rollup specified here with the chain ID
+ // and a unique height for the rollup
+ daClient = getClient(clientState.DALayer)
+ verifyMembership(
+ daClient,
+ header.DAProofHeight,
+ 0,
+ 0,
+ header.DAHeaderProof,
+ DAHeaderPath(clientState.chainId, header.height),
+ header)
+ verifyMembership(
+ daClient,
+ header.DAProofHeight,
+ 0,
+ 0,
+ header.DABlockDataProof,
+ DABlockDataPath(clientState.chainID, header.height),
+ header.blockData)
+
+ // if the rollup is a ZK rollup, then we can verify the correctness immediately.
+ // Otherwise, the correctness of the submitted rollup header is contingent on passing
+ // the fraud period without a valid proof being submitted (see misbehaviour logic)
+ prove(client.ZKProvingCircuit, header.zkProof)
+}
+```
+
+### `updateState`
+
+The `updateState` function for rollups works the same as typical clients, though it is critical that the optimistic rollup client stores the submit time for when the consensus state was created so that we can verify that the fraud period has passed.
+
+```typescript
+function updateState(clientMessage: ClientMessage) {
+ // marshalling logic omitted
+ header = Header(clientMessage)
+ consensusState = ConsensusState{header.timestamp, header.appHash}
+
+ provableStore.set("clients/{clientMessage.clientId}/consensusStates/{header.GetHeight()}", consensusState)
+
+ // create mapping between consensus state and the current time for fraud proof waiting period
+ provableStore.set("clients/{clientMessage.clientId}/processedTimes/{header.GetHeight()}", currentTimestamp())
+}
+```
+
+### `checkForMisbehaviour`
+
+Misbehaviour verification has a different purpose for rollup architectures than it does in traditional consensus mechanisms.
+
+Typical consensus mechanisms, like proof-of-stake, are self-reliant on ordering. Thus, we must have mechanisms to detect when the consensus set is violating the ordering rules. For example, in tendermint, the misbehaviour verification checks that header times are monotonically increasing and that there exists only one valid header for each height.
+
+However, with rollups the ordering is derived from the data availability layer. Thus, even if there is a consensus violation in the rollup consensus, it can be resolved by the DA layer and the consensus rules of the rollup. E.g. even if the sequencer signs multiple blocks at the same height, the canonical block is the first block submitted to the DA layer.
+
+Thus, so long as the verification method encodes the consensus rules of the rollup architecture correctly (for instance, ensuring the header submitted is the earliest one for the given height), then there is no need to verify misbehaviour of the rollup consensus. The consensus is derived from the DA layer, and so if the DA client is frozen due to misbehaviour, this should halt proof verification in the rollup client as well.
+
+Instead, the misbehaviour most relevant for rollups is in the application layer, as the transactions are executed by the sequencer but not by the underlying data availability layer. For ZK rollups, the application is already proven correct so there is no need for application misbehaviour verification. However, optimistic rollups must provide the ability for off-chain processes to submit a proof that the application hash submitted in the header was the result of an incorrect computation of transaction(s) in the block i.e. a fraud proof.
+
+The optimistic fraud proof verifier, or proving circuit, should be implemented as a smart contract, since the fraud prover depends not on the consensus mechanism, but on the application state machine itself. Thus each rollup instance needs its own fraud prover. Having each fraud prover encoded directly in the client requires a different implementation for each rollup instance. Instead, calling out to a separate smart contract allows the client to be reused for all instances, and for new fraud provers to be uploaded for a new rollup application.
+
+```typescript
+// optimistic rollup fraud proof
+// the misbehaviour must be associated with a height on the rollup
+function checkForMisbehaviour(clientMessage: ClientMessage) {
+ // unmarshalling logic omitted
+ misbehaviour = Misbehaviour(clientMessage)
+ clientId = clientMessage.clientId
+ clientState = provableStore.get("clients/{clientMessage.clientId}/clientState")
+
+ // if the rollup has a settlement layer, we can delegate the fraud proof game to the settlement layer
+ // and simply verify with the settlement client that fraud has been proven for the given misbehaviour
+ if clientState.settlementLayer == nil {
+ // fraud prover here is a contract so the same rollup client implementation may
+ // be initiated with different fraud prover contracts for each
+ // different state machine
+ fraudProverContract = getFraudProver(clientId)
+ fraudProverContract.verifyFraudProof(misbehaviour)
+ } else {
+ // in order to use a settlement client some sentinel value signifying submitted misbehaviour
+ // must be stored at a specific path for the given rollup and height
+ // so that the client can prove that the settlement client did in fact successfully prove misbehaviour
+ // for the given rollup at the given height
+ misbehavingHeight = getHeight(misbehaviour)
+ settlementClient = getClient(clientState.settlementLayer)
+ misbehaviourPath = getMisbehaviourPath(clientId, misbehavingHeight)
+ settlementClient.verifyMembership(misbehaviour.proofHeight, 0, 0, misbehaviour.proof, misbehaviourPath, MISBEHAVIOUR_SUCCESS_VALUE)
+ }
+}
+```
+
+### `updateStateOnMisbehaviour`
+
+The misbehaviour update is also dependent on the rollup architecture. In sovereign proof-of-stake chains, if the consensus rules are violated, there is often no fallback mechanism as the trust in the chain is completely destroyed without out-of-protocol social consensus restarting the chain with a new validator set. Thus, for sovereign chains, a client should simply be disabled upon receiving valid misbehaviour.
+
+Rollups on the other hand do have a fallback layer in the data availability and settlement layers. For example, the settlement layer can verify a block is invalid and simply remove it thus enforcing that blocks can keep proceeding with valid states as the settlement layer can continue removing invalid blocks from the chain history. Similarly, it's possible that the settlement layer has a mechanism to switch the sequencer if a block is proven invalid.
+
+Thus, `updateStateOnMisbehaviour` can be less strict for rollups and simply remove the fraudulent consensus state and wait for the resolution as specified by the rollup's consensus rules.
+
+```typescript
+function updateStateOnMisbehaviour(clientMessage: ClientMessage) {
+ // unmarshalling logic omitted
+ misbehaviour = Misbehaviour(clientMessage)
+ misbehavingHeight = getHeight(misbehaviour)
+
+ // delete the fraudulent consensus state
+ deleteConsensusState(clientMessage.clientId, misbehavingHeight)
+
+ // its possible for the rollup client to do additional logic here
+ // e.g. verify the next sequencer chosen from settlement layer
+ // however this is highly specific to rollup architectures
+ // and is not necessary for all rollup architectures
+ // so it will not be modelled here.
+}
+```
+
+### Membership Verification Methods
+
+The parts of the client that rely on the data availability are encapsulated in verifying new rollup blocks. Thus, once they are already added to the client, they can be used for proof verification without reference to the underlying data availability layer. For optimistic rollups, the consensus state must exist in the client for the full fraud period before it can be used for proof verification.
+
+Since the rollup client is dependent on underlying clients: data availability client and settlement client, these must also not be frozen by misbehaviour in order for proof verification to proceed.
+
+```typescript
+function verifyMembership(
+ clientState: ClientState,
+ height: Height,
+ delayPeriodTime: uint64, // disabled
+ delayPeriodBlocks: uint64, // disabled
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: bytes
+): Error {
+ // check conditional clients are still valid
+ daClient = getClient(clientState.DALayer)
+ settlementClient = getClient(clientState.settlementLayer) // may not exist for all rollups
+
+ assert(isActive(clientState))
+ assert(isActive(daClient))
+ assert(isActive(settlementClient))
+
+ consensusState = provableStore.get("clients/{clientState.clientId}/consensusStates/{height}")
+ processedTime = provableStore.set("clients/{clientState.clientId}/processedTimes/{height}")
+
+ // must ensure fraud proof period has passed
+ assert(processedTime + clientState.fraudPeriod > currentTimestamp())
+
+ if !verifyMembership(consensusState.commitmentRoot, proof, path, value) {
+ return error
+ }
+ return nil
+}
+
+function verifyNonMembership(
+ clientState: ClientState,
+ height: Height,
+ delayPeriodTime: uint64, // disabled
+ delayPeriodBlocks: uint64, // disabled
+ proof: CommitmentProof,
+ path: CommitmentPath,
+): Error {
+ // check conditional clients are still valid
+ daClient = getClient(clientState.DALayer)
+ settlementClient = getClient(clientState.settlementLayer) // may not exist for all rollups
+
+ assert(isActive(clientState))
+ assert(isActive(daClient))
+ assert(isActive(settlementClient))
+
+ consensusState = provableStore.get("clients/{clientState.clientId}/consensusStates/{height}")
+ processedTime = provableStore.set("clients/{clientState.clientId}/processedTimes/{height}")
+
+ // must ensure fraud proof period has passed
+ assert(processedTime + clientState.fraudPeriod > currentTimestamp())
+
+ if !verifyNonMembership(consensusState.commitmentRoot, proof, path) {
+ return error
+ }
+ return nil
+}
+```
diff --git a/ibc/next/spec/client/ics-006-solo-machine-client/README.mdx b/ibc/next/spec/client/ics-006-solo-machine-client/README.mdx
new file mode 100644
index 00000000..0b1165dc
--- /dev/null
+++ b/ibc/next/spec/client/ics-006-solo-machine-client/README.mdx
@@ -0,0 +1,382 @@
+---
+ics: 6
+title: Solo Machine Client
+stage: draft
+category: IBC/TAO
+kind: instantiation
+implements: [2]
+version_compatibility:
+ - ibc-go v7.3.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-12-09
+modified: 2019-12-09
+---
+
+## Synopsis
+
+This specification document describes a client (verification algorithm) for a solo machine with a single updateable public key which implements the [ICS 2](../../core/ics-002-client-semantics) interface.
+
+### Motivation
+
+Solo machines — which might be devices such as phones, browsers, or laptops — might like to interface with other machines & replicated ledgers which speak IBC, and they can do so through the uniform client interface.
+
+Solo machine clients are roughly analogous to "implicit accounts" and can be used in lieu of "regular transactions" on a ledger, allowing all transactions to work through the unified interface of IBC.
+
+### Definitions
+
+Functions & terms are as defined in [ICS 2](../../core/ics-002-client-semantics).
+
+`getCommitmentPrefix` is as defined in [ICS 24](../../core/ics-024-host-requirements).
+
+`removePrefix` is as defined in [ICS 23](../../core/ics-023-vector-commitments).
+
+### Desired properties
+
+This specification must satisfy the client interface defined in [ICS 2](../../core/ics-002-client-semantics).
+
+Conceptually, we assume "big table of signatures in the universe" - that signatures produced are public - and incorporate replay protection accordingly.
+
+## Technical specification
+
+This specification contains implementations for all of the functions defined by [ICS 2](../../core/ics-002-client-semantics).
+
+### Client state
+
+The `ClientState` of a solo machine consists of the sequence number and a boolean indicating whether or not the client is frozen.
+
+```typescript
+interface ClientState {
+ sequence: uint64
+ frozen: boolean
+ consensusState: ConsensusState
+}
+```
+
+### Consensus state
+
+The `ConsensusState` of a solo machine consists of the current public key, current diversifier, and timestamp.
+
+The diversifier is an arbitrary string, chosen when the client is created, designed to allow the same public key to be re-used across different solo machine clients (potentially on different chains) without being considered misbehaviour.
+
+```typescript
+interface ConsensusState {
+ publicKey: PublicKey
+ diversifier: string
+ timestamp: uint64
+}
+```
+
+### Height
+
+The `Height` of a solo machine is just a `uint64`, with the usual comparison operations.
+
+### Headers
+
+`Header`s must only be provided by a solo machine when the machine wishes to update the public key or diversifier.
+
+```typescript
+interface Header {
+ sequence: uint64 // deprecated
+ timestamp: uint64
+ signature: Signature
+ newPublicKey: PublicKey
+ newDiversifier: string
+}
+```
+
+`Header` implements the `ClientMessage` interface.
+
+### Signature verification
+
+The solomachine public key must sign over the following struct:
+
+```typescript
+interface SignBytes {
+ sequence: uint64
+ timestamp: uint64
+ diversifier: string
+ path: []byte
+ data: []byte
+}
+```
+
+### Misbehaviour
+
+`Misbehaviour` for solo machines consists of a sequence and two signatures over different messages at that sequence.
+
+```typescript
+interface SignatureAndData {
+ sig: Signature
+ path: []byte
+ data: []byte
+ timestamp: Timestamp
+}
+
+interface Misbehaviour {
+ sequence: uint64
+ signatureOne: SignatureAndData
+ signatureTwo: SignatureAndData
+}
+```
+
+`Misbehaviour` implements the `ClientMessage` interface.
+
+### Signatures
+
+Signatures are provided in the `Proof` field of client state verification functions. They include data & a timestamp, which must also be signed over.
+
+```typescript
+interface Signature {
+ data: []byte
+ timestamp: uint64
+}
+```
+
+### Client initialisation
+
+The solo machine client `initialise` function starts an unfrozen client with the initial consensus state.
+
+```typescript
+function initialise(identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) {
+ assert(clientState.consensusState === consensusState)
+
+ provableStore.set("clients/{identifier}/clientState", clientState)
+ provableStore.set("clients/{identifier}/consensusStates/{height}", consensusState)
+}
+```
+
+The solo machine client `latestClientHeight` function returns the latest sequence.
+
+```typescript
+function latestClientHeight(clientState: ClientState): uint64 {
+ return clientState.sequence
+}
+```
+
+### Validity predicate
+
+The solo machine client `verifyClientMessage` function checks that the currently registered public key signed over the client message at the expected sequence with the current diversifier included in the client message. If the client message is an update, then it must be the current sequence. If the client message is misbehaviour then it must be the sequence of the misbehaviour.
+
+```typescript
+function verifyClientMessage(clientMsg: ClientMessage) {
+ switch typeof(ClientMessage) {
+ case Header:
+ verifyHeader(clientMessage)
+ // misbehaviour only supported for current public key and diversifier on solomachine
+ case Misbehaviour:
+ verifyMisbehaviour(clientMessage)
+ }
+}
+
+function verifyHeader(header: header) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ assert(header.timestamp >= clientstate.consensusState.timestamp)
+ headerData = {
+ newPubKey: header.newPubKey,
+ newDiversifier: header.newDiversifier,
+ }
+ signBytes = SignBytes(
+ sequence: clientState.sequence,
+ timestamp: header.timestamp,
+ diversifier: clientState.consensusState.diversifier,
+ path: []byte{"solomachine:header"},
+ value: marshal(headerData)
+ )
+ assert(checkSignature(cs.consensusState.publicKey, signBytes, header.signature))
+}
+
+function verifyMisbehaviour(misbehaviour: Misbehaviour) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ s1 = misbehaviour.signatureOne
+ s2 = misbehaviour.signatureTwo
+ pubkey = clientState.consensusState.publicKey
+ diversifier = clientState.consensusState.diversifier
+ // assert that the signatures validate and that they are different
+ sigBytes1 = SignBytes(
+ sequence: misbehaviour.sequence,
+ timestamp: s1.timestamp,
+ diversifier: diversifier,
+ path: s1.path,
+ data: s1.data
+ )
+ sigBytes2 = SignBytes(
+ sequence: misbehaviour.sequence,
+ timestamp: s2.timestamp,
+ diversifier: diversifier,
+ path: s2.path,
+ data: s2.data
+ )
+ // either the path or data must be different in order for the misbehaviour to be valid
+ assert(s1.path != s2.path || s1.data != s2.data)
+ assert(checkSignature(pubkey, sigBytes1, misbehaviour.signatureOne.signature))
+ assert(checkSignature(pubkey, sigBytes2, misbehaviour.signatureTwo.signature))
+}
+```
+
+### Misbehaviour predicate
+
+Since misbehaviour is checked in `verifyClientMessage`, if the client message is of type `Misbehaviour` then we return true:
+
+```typescript
+function checkForMisbehaviour(clientMessage: ClientMessage): bool {
+ switch typeof(ClientMessage) {
+ case Misbehaviour:
+ return true
+ }
+ return false
+}
+```
+
+### Update functions
+
+Function `updateState` updates the solo machine `ConsensusState` values using the provided client message header:
+
+```typescript
+function updateState(clientMessage: ClientMessage) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ header = Header(clientMessage)
+ clientState.consensusState.publicKey = header.newPubKey
+ clientState.consensusState.diversifier = header.newDiversifier
+ clientState.consensusState.timestamp = header.timestamp
+ clientState.sequence++
+ provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
+}
+```
+
+Function `updateStateOnMisbehaviour` updates the function after receiving valid misbehaviour:
+
+```typescript
+function updateStateOnMisbehaviour(clientMessage: ClientMessage) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ // freeze the client
+ clientState.frozen = true
+ provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
+}
+```
+
+### State verification functions
+
+All solo machine client state verification functions simply check a signature, which must be provided by the solo machine.
+
+```typescript
+function verifyMembership(
+ clientState: ClientState,
+ // provided height is unnecessary for solomachine
+ // since clientState maintains the expected sequence
+ height: uint64,
+ // delayPeriod is unsupported on solomachines
+ // thus these fields are ignored
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: []byte
+): Error {
+ // the expected sequence used in the signature
+ abortTransactionUnless(!clientState.frozen)
+ abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp)
+
+ // path is prefixed with the store prefix of the commitment proof
+ // e.g. in ibc-go implementation this is "ibc"
+ // since solomachines do not use multi-stores, the prefix needs
+ // to be removed from the path to retrieve the correct key in the
+ // solomachine store
+ unprefixedPath = removePrefix(getCommitmentPrefix(), path)
+ signBytes = SignBytes(
+ sequence: clientState.sequence,
+ timestamp: proof.timestamp,
+ diversifier: clientState.consensusState.diversifier,
+ path: unprefixedPath,
+ data: value,
+ )
+ proven = checkSignature(clientState.consensusState.publicKey, signBytes, proof.sig)
+ if !proven {
+ return error
+ }
+
+ // increment sequence on each verification to provide
+ // replay protection
+ clientState.sequence++
+ clientState.consensusState.timestamp = proof.timestamp
+ // unlike other clients, we must set the client state here because we
+ // mutate the clientState (increment sequence and set timestamp)
+ // thus the verification methods are stateful for the solomachine
+ // in order to prevent replay attacks
+ provableStore.set("clients/{identifier}/clientState", clientState)
+ return nil
+}
+
+function verifyNonMembership(
+ clientState: ClientState,
+ // provided height is unnecessary for solomachine
+ // since clientState maintains the expected sequence
+ height: uint64,
+ // delayPeriod is unsupported on solomachines
+ // thus these fields are ignored
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath
+): Error {
+ abortTransactionUnless(!clientState.frozen)
+ abortTransactionUnless(proof.timestamp >= clientState.consensusState.timestamp)
+
+ // path is prefixed with the store prefix of the commitment proof
+ // e.g. in ibc-go implementation this is "ibc"
+ // since solomachines do not use multi-stores, the prefix needs
+ // to be removed from the path to retrieve the correct key in the
+ // solomachine store
+ unprefixedPath = removePrefix(getCommitmentPrefix(), path)
+ signBytes = SignBytes(
+ sequence: clientState.sequence,
+ timestamp: proof.timestamp,
+ diversifier: clientState.consensusState.diversifier,
+ path: unprefixedPath,
+ data: nil,
+ )
+ proven = checkSignature(clientState.consensusState.publicKey, signBytes, proof.sig)
+ if !proven {
+ return error
+ }
+
+ // increment sequence on each verification to provide
+ // replay protection
+ clientState.sequence++
+ clientState.consensusState.timestamp = proof.timestamp
+ // unlike other clients, we must set the client state here because we
+ // mutate the clientState (increment sequence and set timestamp)
+ // thus the verification methods are stateful for the solomachine
+ // in order to prevent replay attacks
+ provableStore.set("clients/{identifier}/clientState", clientState)
+ return nil
+}
+```
+
+### Properties & invariants
+
+Instantiates the interface defined in [ICS 2](../../core/ics-002-client-semantics).
+
+## Backwards compatibility
+
+Not applicable.
+
+## Forwards compatibility
+
+Not applicable. Alterations to the client verification algorithm will require a new client standard.
+
+## Example implementations
+
+- Implementation of ICS 06 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## History
+
+December 9th, 2019 - Initial version
+December 17th, 2019 - Final first draft
+August 15th, 2022 - Changes to align with 02-client-refactor in [\#813](https://github.com/cosmos/ibc/pull/813)
+September 14th, 2022 - Changes to align with changes in [\#4429](https://github.com/cosmos/ibc-go/pull/4429)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/client/ics-007-tendermint-client/README.mdx b/ibc/next/spec/client/ics-007-tendermint-client/README.mdx
new file mode 100644
index 00000000..230d32fd
--- /dev/null
+++ b/ibc/next/spec/client/ics-007-tendermint-client/README.mdx
@@ -0,0 +1,462 @@
+---
+ics: 7
+title: Tendermint Client
+stage: draft
+category: IBC/TAO
+kind: instantiation
+implements: [2]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-12-10
+modified: 2019-12-19
+---
+
+## Synopsis
+
+This specification document describes a client (verification algorithm) for a blockchain using Tendermint consensus.
+
+### Motivation
+
+State machines of various sorts replicated using the Tendermint consensus algorithm might like to interface with other replicated state machines or solo machines over IBC.
+
+### Definitions
+
+Functions & terms are as defined in [ICS 2](../../core/ics-002-client-semantics).
+
+`currentTimestamp` is as defined in [ICS 24](../../core/ics-024-host-requirements).
+
+The Tendermint light client uses the generalised Merkle proof format as defined in [ICS 23](../../core/ics-023-vector-commitments).
+
+`hash` is a generic collision-resistant hash function, and can easily be configured.
+
+### Desired Properties
+
+This specification must satisfy the client interface defined in ICS 2.
+
+#### Note on "would-have-been-fooled logic
+
+The basic idea of "would-have-been-fooled" detection is that it allows us to be a bit more conservative, and freeze our light client when we know that another light client somewhere else on the network with a slightly different update pattern could have been fooled, even though we weren't.
+
+Consider a topology of three chains - `A`, `B`, and `C`, and two clients for chain `A`, `A_1` and `A_2`, running on chains `B` and `C` respectively. The following sequence of events occurs:
+
+- Chain `A` produces a block at height `h_0` (correctly).
+- Clients `A_1` and `A_2` are updated to the block at height `h_0`.
+- Chain `A` produces a block at height `h_0 + n` (correctly).
+- Client `A_1` is updated to the block at height `h_0 + n` (client `A_2` is not yet updated).
+- Chain `A` produces a second (equivocating) block at height `h_0 + k`, where `k <= n`.
+
+*Without* "would-have-been-fooled", it will be possible to freeze client `A_2` (since there are two valid blocks at height `h_0 + k` which are newer than the latest header `A_2` knows), but it will *not* be possible to freeze `A_1`, since `A_1` has already progressed beyond `h_0 + k`.
+
+Arguably, this is disadvantageous, since `A_1` was just "lucky" in having been updated when `A_2` was not, and clearly some Byzantine fault has happened that should probably be dealt with by human or governance system intervention. The idea of "would-have-been-fooled" is to allow this to be detected by having `A_1` start from a configurable past header to detect misbehaviour (so in this case, `A_1` would be able to start from `h_0` and would also be frozen).
+
+There is a free parameter here - namely, how far back is `A_1` willing to go (how big can `n` be where `A_1` will still be willing to look up `h_0`, having been updated to `h_0 + n`)? There is also a countervailing concern, in and of that double-signing is presumed to be costless after the unbonding period has passed, and we don't want to open up a denial-of-service vector for IBC clients.
+
+The necessary condition is thus that `A_1` should be willing to look up headers as old as it has stored, but should also enforce the "unbonding period" check on the misbehaviour, and avoid freezing the client if the misbehaviour is older than the unbonding period (relative to the client's local timestamp). If there are concerns about clock skew a slight delta could be added.
+
+## Technical Specification
+
+This specification depends on correct instantiation of the [Tendermint consensus algorithm](https://github.com/tendermint/spec/blob/master/spec/consensus/consensus.md) and [light client algorithm](https://github.com/tendermint/spec/blob/master/spec/light-client).
+
+### Client state
+
+The Tendermint client state tracks the current revision, current validator set, trusting period, unbonding period, latest height, latest timestamp (block time), and a possible frozen height.
+
+```typescript
+interface ClientState {
+ chainID: string
+ trustLevel: Rational
+ trustingPeriod: uint64
+ unbondingPeriod: uint64
+ latestHeight: Height
+ frozenHeight: Maybe
+ upgradePath: []string
+ maxClockDrift: uint64
+ proofSpecs: []ProofSpec
+}
+```
+
+### Consensus state
+
+The Tendermint client tracks the timestamp (block time), the hash of the next validator set, and commitment root for all previously verified consensus states (these can be pruned after the unbonding period has passed, but should not be pruned beforehand).
+
+```typescript
+interface ConsensusState {
+ timestamp: uint64
+ nextValidatorsHash: []byte
+ commitmentRoot: []byte
+}
+```
+
+### Height
+
+The height of a Tendermint client consists of two `uint64`s: the revision number, and the height in the revision.
+
+```typescript
+interface Height {
+ revisionNumber: uint64
+ revisionHeight: uint64
+}
+```
+
+Comparison between heights is implemented as follows:
+
+```typescript
+function compare(a: TendermintHeight, b: TendermintHeight): Ord {
+ if (a.revisionNumber < b.revisionNumber)
+ return LT
+ else if (a.revisionNumber === b.revisionNumber)
+ if (a.revisionHeight < b.revisionHeight)
+ return LT
+ else if (a.revisionHeight === b.revisionHeight)
+ return EQ
+ return GT
+}
+```
+
+This is designed to allow the height to reset to `0` while the revision number increases by one in order to preserve timeouts through zero-height upgrades.
+
+### Headers
+
+The Tendermint headers include the height, the timestamp, the commitment root, the hash of the validator set, the hash of the next validator set, and the signatures by the validators who committed the block. The header submitted to the on-chain client also includes the entire validator set, and a trusted height and validator set to update from. This reduces the amount of state maintained by the on-chain client and prevents race conditions on relayer updates.
+
+```typescript
+interface TendermintSignedHeader {
+ height: uint64
+ timestamp: uint64
+ commitmentRoot: []byte
+ validatorsHash: []byte
+ nextValidatorsHash: []byte
+ signatures: []Signature
+}
+```
+
+```typescript
+interface Header {
+ TendermintSignedHeader
+ identifier: string
+ validatorSet: List>
+ trustedHeight: Height
+ trustedValidatorSet: List>
+}
+
+// GetHeight will return the header Height in the IBC ClientHeight
+// format.
+// Implementations may use the revision number to increment the height
+// across height-resetting upgrades. See ibc-go for an example
+func (Header) GetHeight() {
+ return Height{0, height}
+}
+```
+
+Header implements `ClientMessage` interface.
+
+### `Misbehaviour`
+
+The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable.
+Tendermint client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid.
+
+```typescript
+interface Misbehaviour {
+ identifier: string
+ h1: Header
+ h2: Header
+}
+```
+
+Misbehaviour implements `ClientMessage` interface.
+
+### Client initialisation
+
+Tendermint client initialisation requires a (subjectively chosen) latest consensus state, including the full validator set.
+
+```typescript
+function initialise(
+ identifier: Identifier,
+ clientState: ClientState,
+ consensusState: ConsensusState
+) {
+ assert(clientState.trustingPeriod < clientState.unbondingPeriod)
+ assert(clientState.height > 0)
+ assert(clientState.trustLevel >= 1/3 && clientState.trustLevel <= 1)
+
+ provableStore.set("clients/{identifier}/clientState", clientState)
+ provableStore.set("clients/{identifier}/consensusStates/{height}", consensusState)
+}
+```
+
+The Tendermint client `latestClientHeight` function returns the latest stored height, which is updated every time a new (more recent) header is validated.
+
+```typescript
+function latestClientHeight(clientState: ClientState): Height {
+ return clientState.latestHeight
+}
+```
+
+### Validity predicate
+
+Tendermint client validity checking uses the bisection algorithm described in the [Tendermint spec](https://github.com/tendermint/spec/tree/master/spec/consensus/light-client). If the provided header is valid, the client state is updated & the newly verified commitment written to the store.
+
+```typescript
+function verifyClientMessage(clientMsg: ClientMessage) {
+ switch typeof(clientMsg) {
+ case Header:
+ verifyHeader(clientMsg)
+ case Misbehaviour:
+ verifyHeader(clientMsg.h1)
+ verifyHeader(clientMsg.h2)
+ }
+}
+```
+
+Verify validity of regular update to the Tendermint client
+
+```typescript
+function verifyHeader(header: Header) {
+ clientState = provableStore.get("clients/{header.identifier}/clientState")
+ // assert trusting period has not yet passed
+ assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod)
+ // assert header timestamp is less than trust period in the future. This should be resolved with an intermediate header.
+ assert(header.timestamp - clientState.latestTimeStamp < clientState.trustingPeriod)
+ // trusted height revision must be the same as header revision
+ // if revisions are different, use upgrade client instead
+ // trusted height must be less than header height
+ assert(header.GetHeight().revisionNumber == header.trustedHeight.revisionNumber)
+ assert(header.GetHeight().revisionHeight > header.trustedHeight.revisionHeight)
+ // fetch the consensus state at the trusted height
+ consensusState = provableStore.get("clients/{header.identifier}/consensusStates/{header.trustedHeight}")
+ // assert that header's trusted validator set hashes to consensus state's validator hash
+ assert(hash(header.trustedValidatorSet) == consensusState.nextValidatorsHash)
+
+ // call the tendermint client's `verify` function
+ assert(tmClient.verify(
+ header.trustedValidatorSet,
+ clientState.latestHeight,
+ clientState.trustingPeriod,
+ clientState.maxClockDrift,
+ header.TendermintSignedHeader,
+ ))
+}
+```
+
+### Misbehaviour predicate
+
+Function `checkForMisbehaviour` will check if an update contains evidence of Misbehaviour. If the ClientMessage is a header we check for implicit evidence of misbehaviour by checking if there already exists a conflicting consensus state in the store or if the header breaks time monotonicity.
+
+```typescript
+function checkForMisbehaviour(clientMsg: clientMessage): boolean {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ switch typeof(clientMsg) {
+ case Header:
+ // fetch consensusstate at header height if it exists
+ consensusState = provableStore.get("clients/{clientMsg.identifier}/consensusStates/{header.GetHeight()}")
+ // if consensus state exists and conflicts with the header
+ // then the header is evidence of misbehaviour
+ if consensusState != nil &&
+ !(
+ consensusState.timestamp == header.timestamp &&
+ consensusState.commitmentRoot == header.commitmentRoot &&
+ consensusState.nextValidatorsHash == header.nextValidatorsHash
+ ) {
+ return true
+ }
+
+ // check for time monotonicity misbehaviour
+ // if header is not monotonically increasing with respect to neighboring consensus states
+ // then return true
+ // NOTE: implementation must have ability to iterate ascending/descending by height
+ prevConsState = getPreviousConsensusState(header.GetHeight())
+ nextConsState = getNextConsensusState(header.GetHeight())
+ if prevConsState.timestamp >= header.timestamp {
+ return true
+ }
+ if nextConsState != nil && nextConsState.timestamp <= header.timestamp {
+ return true
+ }
+ case Misbehaviour:
+ if (misbehaviour.h1.height < misbehaviour.h2.height) {
+ return false
+ }
+ // if heights are equal check that this is valid misbehaviour of a fork
+ if (misbehaviour.h1.height === misbehaviour.h2.height && misbehaviour.h1.commitmentRoot !== misbehaviour.h2.commitmentRoot) {
+ return true
+ }
+ // otherwise if heights are unequal check that this is valid misbehavior of BFT time violation
+ if (misbehaviour.h1.timestamp <= misbehaviour.h2.timestamp) {
+ return true
+ }
+
+ return false
+ }
+}
+```
+
+### Update state
+
+Function `updateState` will perform a regular update for the Tendermint client. It will add a consensus state to the client store. If the header is higher than the latest height on the `clientState`, then the `clientState` will be updated.
+
+```typescript
+function updateState(clientMsg: clientMessage) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ header = Header(clientMessage)
+ // only update the clientstate if the header height is higher
+ // than clientState latest height
+ if clientState.height < header.GetHeight() {
+ // update latest height
+ clientState.latestHeight = header.GetHeight()
+
+ // save the client
+ provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
+ }
+
+ // create recorded consensus state, save it
+ consensusState = ConsensusState{header.timestamp, header.nextValidatorsHash, header.commitmentRoot}
+ provableStore.set("clients/{clientMsg.identifier}/consensusStates/{header.GetHeight()}", consensusState)
+
+ // these may be stored as private metadata within the client in order to verify
+ // that the delay period has passed in proof verification
+ provableStore.set("clients/{clientMsg.identifier}/processedTimes/{header.GetHeight()}", currentTimestamp())
+ provableStore.set("clients/{clientMsg.identifier}/processedHeights/{header.GetHeight()}", currentHeight())
+}
+```
+
+### Update state on misbehaviour
+
+Function `updateStateOnMisbehaviour` will set the frozen height to a non-zero sentinel height to freeze the entire client.
+
+```typescript
+function updateStateOnMisbehaviour(clientMsg: clientMessage) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ clientState.frozenHeight = Height{0, 1}
+ provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
+}
+```
+
+### Upgrades
+
+The chain which this light client is tracking can elect to write a special pre-determined key in state to allow the light client to update its client state (e.g. with a new chain ID or revision) in preparation for an upgrade.
+
+As the client state change will be performed immediately, once the new client state information is written to the predetermined key, the client will no longer be able to follow blocks on the old chain, so it must upgrade promptly.
+
+```typescript
+function upgradeClientState(
+ clientState: ClientState,
+ newClientState: ClientState,
+ height: Height,
+ proof: CommitmentProof
+) {
+ // assert trusting period has not yet passed
+ assert(currentTimestamp() - clientState.latestTimestamp < clientState.trustingPeriod)
+ // check that the revision has been incremented
+ assert(newClientState.latestHeight.revisionNumber > clientState.latestHeight.revisionNumber)
+ // check proof of updated client state in state at predetermined commitment prefix and key
+ path = applyPrefix(clientState.upgradeCommitmentPrefix, clientState.upgradeKey)
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ // Implementations may choose how to pass in the identifier
+ // ibc-go provides the identifier-prefixed store to this method
+ // so that all state reads are for the client in question
+ consensusState = provableStore.get("clients/{clientIdentifier}/consensusStates/{height}")
+ // verify that the provided consensus state has been stored
+ assert(verifyMembership(consensusState.commitmentRoot, proof, path, newClientState))
+ // update client state
+ clientState = newClientState
+ provableStore.set("clients/{clientIdentifier}/clientState", clientState)
+}
+```
+
+### State verification functions
+
+Tendermint client state verification functions check a Merkle proof against a previously validated commitment root.
+
+These functions utilise the `proofSpecs` with which the client was initialised.
+
+```typescript
+function verifyMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: []byte
+): Error {
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // assert that enough time has elapsed
+ assert(currentTimestamp() >= processedTime + delayPeriodTime)
+ // assert that enough blocks have elapsed
+ assert(currentHeight() >= processedHeight + delayPeriodBlocks)
+ // fetch the previously verified commitment root & verify membership
+ // Implementations may choose how to pass in the identifier
+ // ibc-go provides the identifier-prefixed store to this method
+ // so that all state reads are for the client in question
+ consensusState = provableStore.get("clients/{clientIdentifier}/consensusStates/{height}")
+ // verify that has been stored
+ if !verifyMembership(consensusState.commitmentRoot, proof, path, value) {
+ return error
+ }
+ return nil
+}
+
+function verifyNonMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath
+): Error {
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // assert that enough time has elapsed
+ assert(currentTimestamp() >= processedTime + delayPeriodTime)
+ // assert that enough blocks have elapsed
+ assert(currentHeight() >= processedHeight + delayPeriodBlocks)
+ // fetch the previously verified commitment root & verify membership
+ // Implementations may choose how to pass in the identifier
+ // ibc-go provides the identifier-prefixed store to this method
+ // so that all state reads are for the client in question
+ consensusState = provableStore.get("clients/{clientIdentifier}/consensusStates/{height}")
+ // verify that nothing has been stored at path
+ if !verifyNonMembership(consensusState.commitmentRoot, proof, path) {
+ return error
+ }
+ return nil
+}
+```
+
+### Properties & Invariants
+
+Correctness guarantees as provided by the Tendermint light client algorithm.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Not applicable. Alterations to the client verification algorithm will require a new client standard.
+
+## Example Implementations
+
+- Implementation of ICS 07 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 07 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+December 10th, 2019 - Initial version
+December 19th, 2019 - Final first draft
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/client/ics-008-wasm-client/README.mdx b/ibc/next/spec/client/ics-008-wasm-client/README.mdx
new file mode 100644
index 00000000..34e45760
--- /dev/null
+++ b/ibc/next/spec/client/ics-008-wasm-client/README.mdx
@@ -0,0 +1,582 @@
+---
+ics: 8
+title: Wasm Client
+stage: draft
+category: IBC/TAO
+kind: instantiation
+implements: [2]
+authors:
+ - name: Parth Desai
+ email: parth@chorus.one
+ - name: Mateusz Kaczanowski
+ email: mateusz@chorus.one
+ - name: Blas Rodriguez Irizar
+ email: blas@composable.finance
+ - name: Steve Miskovetz
+ email: steve@strange.love
+created: 2020-10-13
+modified: 2024-03-22
+---
+
+## Synopsis
+
+This specification document describes an interface to a light client stored as a Wasm bytecode for a blockchain.
+
+### Motivation
+
+Currently, adding a new client implementation or upgrading an existing one requires a hard upgrade because the client implementations are part of the static chain binary. Any change to the on-chain light client code depends on chain governance approving an upgrade before it can be deployed.
+
+This may be acceptable when adding new client types since the number of unique consensus algorithms that need to be supported is currently small. However, this process will become very tedious when it comes to upgrading light clients.
+
+Without dynamically upgradable light clients, a chain that wishes to upgrade its consensus algorithm (and thus break existing light clients) must wait for all counterparty chains to perform a hard upgrade that adds support for the upgraded light client before it can perform an upgrade on its chain. Examples of a consensus-breaking upgrade would be an upgrade from Tendermint v1 to a light-client breaking Tendermint v2 or switching from Tendermint consensus to Honeybadger. Changes to the internal state-machine logic will not affect consensus. E.g., changes to the staking module do not require an IBC upgrade.
+
+Requiring all counterparties to add statically new client implementations to their binaries will inevitably slow the pace of upgrades in the IBC network since the deployment of an upgrade on even a very experimental, fast-moving chain will be blocked by an upgrade to a high-value chain that will be inherently more conservative.
+
+Once the IBC network broadly adopts dynamically upgradable clients, a chain may upgrade its consensus algorithm whenever it wishes, and relayers may upgrade the client code of all counterparty chains without requiring the counterparty chains to perform an upgrade themselves. This prevents a dependency on counterparty chains when considering upgrading one's consensus algorithm.
+
+Another reason why this interface is beneficial is that it removes the dependency between Light clients and the Go programming language. Using Wasm as a compilation target, light clients can be written in any programming language whose toolchain includes Wasm as a compilation target. Examples of these are Go, Rust, C, and C++.
+
+### Definitions
+
+Functions & terms are as defined in [ICS 2](../../core/ics-002-client-semantics).
+
+`currentTimestamp` is as defined in [ICS 24](../../core/ics-024-host-requirements).
+
+`Wasm VM` refers to a virtual machine capable of executing valid Wasm bytecode.
+
+`Wasm Contract` refers to Wasm bytecode stored in the Wasm VM, which provides a target blockchain specific implementation of [ICS 2](../../core/ics-002-client-semantics).
+
+`Wasm Client Proxy` refers to an implementation of ICS 8 that acts as a pass-through to the Wasm client.
+
+`Wasm Client` refers to a particular instance of `Wasm Contract` defined as a tuple `(Wasm Contract, ClientID)`.
+
+### Desired properties
+
+This specification must satisfy the client interface defined in [ICS 2.](../../core/ics-002-client-semantics).
+
+## Technical specification
+
+This specification depends on the correct instantiation of the Wasm client and is decoupled from any specific implementation of the target `blockchain` consensus algorithm.
+
+### Storage management
+
+Light client operations defined in [ICS 2](../../core/ics-002-client-semantics) can be stateful; they may modify the
+state kept in storage. For that, there is a need to allow the underlying Wasm light client implementation
+to access client and consensus data structures and, after performing certain computations, to
+update the storage with the new versions of them.
+
+For this reason, the implementation in ibc-go chooses to share the Wasm client store between the `02-client` module (for reading), `08-wasm` module (for instantiation), and Wasm contract. Other than instantiation, the Wasm contract is responsible for updating state.
+
+### Wasm VM
+
+The purpose of this module is to delegate light client logic to a module written in Wasm. For that,
+the Wasm client proxy needs a reference (or a handler) to a Wasm VM. The Wasm client proxy can then directly call the [`wasmvm`](https://github.com/CosmWasm/wasmvm) to interact with the VM with less overhead, fewer dependencies, and finer grain control over the Wasm client store than if using an intermediary module such as [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/v0.41.0/x/wasm).
+
+### Gas costs
+
+[`wasmd`](https://github.com/CosmWasm/wasmd) has thoroughly benchmarked [gas adjustments for CosmWasm](https://github.com/CosmWasm/wasmd/blob/v0.41.0/x/wasm/keeper/gas_register.go#L13-L56) and the same values are being applied in the Wasm VM used in ibc-go's implementation of ICS 8.
+
+```typescript
+const (
+ DefaultGasMultiplier uint64 = 140_000_000
+ DefaultInstanceCost uint64 = 60_000
+ DefaultCompileCost uint64 = 3
+ DefaultContractMessageDataCost uint64 = 0
+ DefaultDeserializationCostPerByte = 1
+)
+```
+
+### Client state
+
+The Wasm client state tracks the location of the Wasm bytecode via `checksum`. Binary data represented by the `data` field is opaque and only interpreted by the Wasm contract.
+
+```typescript
+interface ClientState {
+ data: []byte
+ checksum: []byte
+ latestHeight: Height
+}
+```
+
+### Consensus state
+
+The Wasm consensus state tracks the consensus state of the Wasm client. Binary data represented by the `data` field is opaque and only interpreted by the Wasm contract.
+
+```typescript
+interface ConsensusState {
+ data: []byte
+}
+```
+
+### Height
+
+The height of a Wasm light client instance consists of two `uint64`s: the revision number and the height in the revision.
+
+```typescript
+interface Height {
+ revisionNumber: uint64
+ revisionHeight: uint64
+}
+```
+
+### Headers
+
+Contents of Wasm client headers depend upon Wasm contract. Binary data represented by the `data` field is opaque and only interpreted by the Wasm contract, and will consist either of a valid header or of two conflicting headers, both of which the Wasm contract would have considered valid. In the latter case, the contract will update the consensus state with the valid header; in the former case, the light client may detect misbehaviour and freeze the client (thus preventing further packet flow).
+
+```typescript
+interface Header {
+ data: []byte
+}
+```
+
+### Client initialization
+
+Wasm client initialization requires a (subjectively chosen) latest consensus state and corresponding client state, interpretable by the Wasm contract.
+
+```typescript
+interface InstantiateMessage {
+ consensusState: ConsensusState
+}
+```
+
+```typescript
+function initialise(
+ identifier: Identifier,
+ data: []byte,
+ checksum: []byte,
+ consensusState: ConsensusState,
+ height: Height
+): ClientState {
+ // bytes of encoded consensus state of base light
+ // client are passed in the message
+ payload = InstantiateMessage{consensusState.data}
+
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{identifier}")
+
+ // initialize wasm contract for a previously stored contract identified by checksum
+ initContract(checksum, clientStore, marshalJSON(payload))
+
+ return ClientState{
+ data,
+ checksum,
+ latestHeight: height
+ }
+}
+```
+
+### Contract payload messages
+
+The Wasm client proxy performs calls to the Wasm client via the Wasm VM. The calls require as input payload messages that are categorized on two discriminated union types: one for payload messages used in calls that perform only reads, and one for payload messages used in calls that perform state-changing writes.
+
+```typescript
+type QueryMsg =
+ | Status
+ | TimestampAtHeight
+ | VerifyClientMessage
+ | CheckForMisbehaviour;
+```
+
+``` typescript
+type SudoMsg =
+ | UpdateState
+ | UpdateStateOnMisbehaviour
+ | VerifyMembership
+ | VerifyNonMembership
+ | VerifyUpgradeAndUpdateState
+ | CheckSubstituteAndUpdateState
+```
+
+### Validity predicate
+
+Wasm client validity checking uses underlying Wasm contract. If the provided client message is valid, the client state will proceed to checking for misbehaviour (call to `checkForMisbehaviour`) and updating state (call to either `updateStateOnMisbehaviour` or `updateState` depending on whether misbehaviour was detected in the client message).
+
+```typescript
+interface VerifyClientMessageMsg {
+ clientMessage: bytes
+}
+```
+
+```typescript
+function verifyClientMessage(clientMsg: ClientMessage) {
+ // bytes of encoded client message of base light
+ // client are passed in the message
+ payload = verifyClientMessageMsg{clientMsg.data}
+
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientMsg.identifier}")
+
+ // use underlying wasm contract to verify client message
+ assert(callContract(clientStore, clientState, marshalJSON(payload)))
+}
+```
+
+### Misbehaviour predicate
+
+Function `checkForMisbehaviour` will check if an update contains evidence of misbehaviour. Wasm client misbehaviour checking determines whether or not two conflicting headers at the same height would have convinced the light client.
+
+```typescript
+interface CheckForMisbehaviourMsg {
+ clientMessage: bytes
+}
+```
+
+```typescript
+function checkForMisbehaviour(clientMsg: ClientMessage): boolean {
+ // bytes of encoded client message of base light
+ // client are passed in the message
+ payload = checkForMisbehaviourMsg{clientMsg.data}
+
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientMsg.identifier}")
+
+ // use underlying wasm contract to check for misbehaviour
+ result = callContract(clientStore, clientState, marshalJSON(payload))
+ return result.foundMisbehaviour
+}
+```
+
+### State update
+
+Function `updateState` will perform a regular update for the Wasm client. It will add a consensus state to the client store. If the header is higher than the latest height on the `clientState`, then the `clientState` will be updated.
+
+```typescript
+interface UpdateStateMsg {
+ clientMessage: bytes
+}
+```
+
+```typescript
+function updateState(clientMsg: ClientMessage) {
+ // bytes of encoded client message of base light
+ // client are passed in the message
+ payload = UpdateStateMsg{clientMsg.data}
+
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientMsg.identifier}")
+
+ // use underlying wasm contract to update client state and store new consensus state
+ callContract(clientStore, clientState, marshalJSON(payload))
+}
+```
+
+### State update on misbehaviour
+
+Function `updateStateOnMisbehaviour` will set the frozen height to a non-zero height to freeze the entire client.
+
+```typescript
+interface UpdateStateOnMisbehaviourMsg {
+ clientMessage: bytes
+}
+```
+
+```typescript
+function updateStateOnMisbehaviour(clientMsg: clientMessage) {
+ // bytes of encoded client message of base light
+ // client are passed in the message
+ payload = UpdateStateOnMisbehaviourMsg{clientMsg.data}
+
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientMsg.identifier}")
+
+ // use underlying wasm contract to update client state
+ callContract(clientStore, clientState, marshalJSON(payload))
+}
+```
+
+### Upgrades
+
+The chain which this light client is tracking can elect to write a special pre-determined key in state to allow the light client to update its client state (e.g. with a new chain ID or revision) in preparation for an upgrade.
+
+As the client state change will be performed immediately, once the new client state information is written to the pre-determined key, the client will no longer be able to follow blocks on the old chain, so it must upgrade promptly.
+
+```typescript
+function upgradeClientState(
+ clientState: ClientState,
+ newClientState: ClientState,
+ height: Height,
+ proof: CommitmentProof
+) {
+ // Use the underlying wasm contract to verify the upgrade and
+ // update the client state. The contract is passed a
+ // client identifier-prefixed store so that all state read/write
+ // operations are for the client in question.
+}
+```
+
+### Proposals
+
+If a Wasm light client becomes frozen, a governance proposal can be submitted to update the state of the frozen light client (the subject) with the state of an active light client (the substitute). The substitute client MUST be of the same type as the subject client. Depending on the exact type of the underlying light client type, all or a subset of parameters of the subject and substitute client states MUST match.
+
+```typescript
+function checkSubstituteAndUpdateState(
+ subjectClientState: ClientState,
+ substituteClientState: ClientState,
+ subjectClientStore: KVStore,
+ substituteClientStore: KVStore
+) {
+ // Use the underlying wasm contract to update the subject
+ // client with the state of the substitute. The contract is
+ // passed a client identifier-prefixed store so that all
+ // state read/write operations are for the client in question.
+}
+```
+
+### State verification functions
+
+Wasm client state verification functions check a proof against a previously validated commitment root.
+
+```typescript
+interface verifyMembershipMsg {
+ height: Height
+ delayTimePeriod: uint64
+ delayBlockPeriod: uint64
+ proof: CommitmentProof
+ path: CommitmentPath
+ value: []byte
+}
+
+interface verifyNonMembershipMsg {
+ height: Height
+ DelayTimePeriod: uint64
+ DelayBlockPeriod: uint64
+ Proof: CommitmentProof
+ Path: CommitmentPath
+}
+```
+
+```typescript
+function verifyMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: []byte
+): Error {
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+
+ payload = VerifyMembershipMsg{
+ height,
+ delayTimePeriod,
+ delayBlockPeriod,
+ proof,
+ path,
+ value
+ }
+
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientIdentifier}")
+
+ // use underlying wasm contract to verify that has been stored
+ result = callContract(clientStore, clientState, marshalJSON(payload))
+ return result.error
+}
+
+function verifyNonMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath
+): Error {
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+
+ payload = verifyNonMembershipMsg{
+ height,
+ delayTimePeriod,
+ delayBlockPeriod,
+ proof,
+ path
+ }
+
+ // retrieve client identifier-prefixed store
+ clientStore = provableStore.prefixStore("clients/{clientIdentifier}")
+
+ // use underlying wasm contract to verify that nothing has been stored at path
+ result = callContract(clientStore, clientState, marshalJSON(payload))
+ return result.error
+}
+```
+
+### Wasm Contract Interface
+
+#### Interaction between Go and Wasm
+
+When an instruction needs to be executed in Wasm code, functions are executed using a `wasmvm`.
+This VM is sandboxed, hence isolated from other operations.
+The process requires packaging all the arguments to be executed by a specific function (including
+pointers to `KVStore`s if needed), pointing to a checksum, and a `sdk.GasMeter` to properly account
+for gas usage during the execution of the function.
+
+#### Contract instantiation
+
+Instantiation of a contract is minimal. No data is passed in the message for the contract call, but the
+Wasm client store is passed. This allows for a Wasm contract to initialize any metadata that they
+need such as processed height and/or processed time.
+
+#### Contract query
+
+Every Wasm contract must support these query messages:
+
+```rust
+#[cw_serde]
+pub struct StatusMsg {}
+
+#[cw_serde]
+pub struct TimestampAtHeightMsg {
+ pub height: Height,
+}
+
+#[cw_serde]
+pub struct VerifyClientMessage {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub client_message: Bytes,
+}
+
+pub struct CheckForMisbehaviourMsgRaw {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub client_message: Bytes,
+}
+```
+
+The response for queries is as follows:
+
+```rust
+#[cw_serde]
+pub struct QueryResponse {
+ pub is_valid: bool,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub status: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub genesis_metadata: Option>, // metadata KV pairs
+ #[serde(skip_serializing_if = "Option::is_none")]
+ // boolean set by contract's implementation of checkForMisbehaviour
+ pub found_misbehaviour: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ // timestamp set by contracts implementation of getTimestampAtHeight
+ pub timestamp: Option,
+}
+
+#[cw_serde]
+pub struct GenesisMetadata {
+ pub key: Vec,
+ pub value: Vec,
+}
+```
+
+#### Contract sudo
+
+Every Wasm contract must support these sudo messages:
+
+```rust
+#[cw_serde]
+pub struct VerifyMembershipMsg {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub proof: Bytes,
+ pub path: MerklePath,
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub value: Bytes,
+ pub height: Height,
+ pub delay_block_period: u64,
+ pub delay_time_period: u64,
+}
+
+#[cw_serde]
+pub struct VerifyNonMembershipMsg {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub proof: Bytes,
+ pub path: MerklePath,
+ pub height: Height,
+ pub delay_block_period: u64,
+ pub delay_time_period: u64,
+}
+
+#[cw_serde]
+pub struct UpdateStateOnMisbehaviourMsg {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub client_message: Bytes,
+}
+
+#[cw_serde]
+pub struct UpdateStateMsg {
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub client_message: Bytes,
+}
+
+#[cw_serde]
+pub struct CheckSubstituteAndUpdateStateMsg {
+ substitute_client_msg: Vec,
+}
+
+#[cw_serde]
+pub struct MigrateClientStoreMsg {}
+
+#[cw_serde]
+pub struct VerifyUpgradeAndUpdateStateMsgRaw {
+ pub upgrade_client_state: Bytes,
+ pub upgrade_consensus_state: Bytes,
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub proof_upgrade_client: Vec,
+ #[schemars(with = "String")]
+ #[serde(with = "Base64", default)]
+ pub proof_upgrade_consensus_state: Vec,
+}
+```
+
+The response for sudo is as follows:
+
+```rust
+#[cw_serde]
+pub struct ContractResult {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ // heights set by contract's implementation of updateState
+ pub heights: Option>,
+}
+```
+
+### Properties & Invariants
+
+Correctness guarantees as provided by the underlying algorithm implemented by Wasm contract.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+As long as Wasm contract keeps its interface consistent with `ICS 02` it should be forward compatible
+
+## Example Implementations
+
+Implementation of ICS 08 in Go can be found in [ibc-go PR](https://github.com/cosmos/ibc-go/pull/3355).
+
+## History
+
+Oct 8, 2021 - Final first draft
+
+Mar 15th, 2022 - Update for 02-client refactor
+
+Sep 7th, 2023 - Update for changes during implementation
+
+Mar 22th, 2024 - Update for changes after release of ibc-go's 08-wasm module
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/client/ics-009-loopback-cilent/README.mdx b/ibc/next/spec/client/ics-009-loopback-cilent/README.mdx
new file mode 100644
index 00000000..c8c04e15
--- /dev/null
+++ b/ibc/next/spec/client/ics-009-loopback-cilent/README.mdx
@@ -0,0 +1,221 @@
+---
+ics: 9
+title: Loopback Client
+stage: draft
+category: IBC/TAO
+kind: instantiation
+author: Christopher Goes
+created: 2020-01-17
+modified: 2023-03-02
+requires: 2
+implements: 2
+---
+## Synopsis
+
+This specification describes a loopback client, designed to be used for interaction over the IBC interface with modules present on the same ledger.
+
+### Motivation
+
+Loopback clients may be useful in cases where the calling module does not have prior knowledge of where precisely the destination module lives and would like to use the uniform IBC message-passing interface (similar to `127.0.0.1` in TCP/IP).
+
+### Definitions
+
+Functions & terms are as defined in [ICS 2](../../core/ics-002-client-semantics).
+
+`ConnectionEnd` and `generateIdentifier` are as defined in [ICS 3](../../core/ics-003-connection-semantics)
+
+`getCommitmentPrefix` is as defined in [ICS 24](../../core/ics-024-host-requirements).
+
+`removePrefix` is as defined in [ICS 23](../../core/ics-023-vector-commitments).
+
+### Desired Properties
+
+Intended client semantics should be preserved, and loopback abstractions should be negligible cost.
+
+## Technical Specification
+
+### Data Structures
+
+No consensus state, headers, or evidence data structures are required for a loopback client. The loopback client does not need to store the consensus state of a remote chain, since state verification does not require to check a Merkle proof against a previously validated commitment root.
+
+```typescript
+type ConsensusState object
+
+type Header object
+
+type Misbehaviour object
+```
+
+### Client state
+
+The loopback client state tracks the latest height of the local ledger.
+
+```typescript
+interface ClientState {
+ latestHeight: Height
+}
+```
+
+### Height
+
+The height of a loopback client state consists of two `uint64`s: the revision number, and the height in the revision.
+
+```typescript
+interface Height {
+ revisionNumber: uint64
+ revisionHeight: uint64
+}
+```
+
+### Sentinel objects
+
+Similarly as in TCP/IP, where there exists a single loopback address, the protocol defines the existence of a single sentinel `ClientState` instance with the client identifier `09-localhost`. Creation of other loopback clients MUST be forbidden.
+
+Additionally, implementations **will** reserve a special connection identifier `connection-localhost` to be used by a single sentinel `ConnectionEnd` stored by default (i.e. at genesis or upgrade). The `clientIdentifier` and `counterpartyClientIdentifier` of the connection end both reference the sentinel `09-localhost` client identifier. The `counterpartyConnectionIdentifier` references the special connection identifier `connection-localhost`. The existence of a sentinel loopback connection end enables IBC applications to establish channels directly on top of the sentinel connection. Channel handshakes can then be initiated by supplying the loopback connection identifier (`connection-localhost`) in the `connectionHops` parameter of the `ChanOpenInit` datagram.
+
+Implementations **may** also allow the creation of more connections associated with the loopback client. These connections would then have a connection identifier as generated by `generateIdentifier`.
+
+### Relayer messages
+
+Relayers supporting localhost packet flow must be adapted to submit messages from sending applications back to the originating chain.
+
+This would require first checking the underlying connection identifier on any channel-level messages. If the underlying connection identifier is `connection-localhost`, then the relayer must construct the message and send it back to the originating chain. The message MUST be constructed with a sentinel byte for the proof (`[]byte{0x01}`), since the loopback client does not need Merkle proofs of the state of a remote ledger; the proof height in the message may be zero, since it is ignored by the loopback client.
+
+Implementations **may** choose to implement loopback such that the next message in the handshake or packet flow is automatically called without relayer-driven transactions. However, implementers must take care to ensure that automatic message execution does not cause gas consumption issues.
+
+### Client initialisation
+
+Loopback client initialisation requires the latest height of the local ledger.
+
+```typescript
+function initialise(identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) {
+ assert(clientState.latestHeight > 0)
+ assert(consensusState === nil)
+
+ provableStore.set("clients/{identifier}/clientState", clientState)
+}
+```
+
+### Validity predicate
+
+No validity checking is necessary in a loopback client; the function should never be called.
+
+```typescript
+function verifyClientMessage(clientMsg: ClientMessage) {
+ assert(false)
+}
+```
+
+### Misbehaviour predicate
+
+No misbehaviour checking is necessary in a loopback client; the function should never be called.
+
+```typescript
+function checkForMisbehaviour(clientMsg: clientMessage) => bool {
+ return false
+}
+```
+
+### Update state
+
+Function `updateState` will perform a regular update for the loopback client. The `clientState` will be updated with the latest height of the local ledger. This function should be called automatically at every height.
+
+```typescript
+function updateState(clientMsg: clientMessage) {
+ clientState = provableStore.get("clients/{clientMsg.identifier}/clientState")
+
+ // retrieve the latest height from the local ledger
+ height = getSelfHeight()
+ clientState.latestHeight = height
+
+ // save the client state
+ provableStore.set("clients/{clientMsg.identifier}/clientState", clientState)
+}
+```
+
+### Update state on misbehaviour
+
+Function `updateStateOnMisbehaviour` is unsupported by the loopback client and performs a no-op.
+
+```typescript
+function updateStateOnMisbehaviour(clientMsg: clientMessage) { }
+```
+
+### State verification functions
+
+State verification functions simply need to read state from the local ledger and compare with the bytes stored under the standardized key paths. The loopback client needs read-only access to the **entire IBC store** of the local ledger, and not only to its own client identifier-prefixed store.
+
+```typescript
+function verifyMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: []byte
+): Error {
+ // path is prefixed with the store prefix of the commitment proof
+ // e.g. in ibc-go implementation this is "ibc"
+ // since verification is done on the IBC store of the local ledger
+ // the prefix needs to be removed from the path to retrieve the
+ // correct key in the store
+ unprefixedPath = removePrefix(getCommitmentPrefix(), path)
+
+ // The complete (not only client identifier-prefixed) store is needed
+ // to verify that a path has been set to a particular value
+ if provableStore.get(unprefixedPath) !== value {
+ return error
+ }
+ return nil
+}
+
+function verifyNonMembership(
+ clientState: ClientState,
+ height: Height,
+ delayTimePeriod: uint64,
+ delayBlockPeriod: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath
+): Error {
+ // path is prefixed with the store prefix of the commitment proof
+ // e.g. in ibc-go implementation this is "ibc"
+ // since verification is done on the IBC store of the local ledger
+ // the prefix needs to be removed from the path to retrieve the
+ // correct key in the store
+ unprefixedPath = removePrefix(getCommitmentPrefix(), path)
+
+ // The complete (not only client identifier-prefixed) store is needed
+ // to verify that a path has not been set to a particular value
+ if provableStore.get(unprefixedPath) !== nil {
+ return error
+ }
+ return nil
+}
+```
+
+### Properties & Invariants
+
+Semantics are as if this were a remote client of the local ledger.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Not applicable. Alterations to the client algorithm will require a new client standard.
+
+## Example Implementations
+
+- Implementation of ICS 09 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## History
+
+January 17, 2020 - Initial version
+
+March 2, 2023 - Update after ICS 02 refactor and addition of sentinel objects.
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/client/ics-010-grandpa-client/README.mdx b/ibc/next/spec/client/ics-010-grandpa-client/README.mdx
new file mode 100644
index 00000000..40ee1873
--- /dev/null
+++ b/ibc/next/spec/client/ics-010-grandpa-client/README.mdx
@@ -0,0 +1,374 @@
+---
+ics: 10
+title: GRANDPA Client
+stage: draft
+category: IBC/TAO
+kind: instantiation
+implements: [2]
+authors:
+ - name: Yuanchao Sun
+ email: ys@cdot.network
+ - name: John Wu
+ email: john@cdot.network
+created: 2020-03-15
+---
+
+## Synopsis
+
+This specification document describes a client (verification algorithm) for a blockchain using GRANDPA.
+
+GRANDPA (GHOST-based Recursive Ancestor Deriving Prefix Agreement) is a finality gadget that will be used by the Polkadot relay chain. It now has a Rust implementation and is part of the Substrate, so likely blockchains built using Substrate will use GRANDPA as its finality gadget.
+
+### Motivation
+
+Blockchains using GRANDPA finality gadget might like to interface with other replicated state machines or solo machines over IBC.
+
+### Definitions
+
+Functions & terms are as defined in [ICS 2](../../core/ics-002-client-semantics).
+
+### Desired Properties
+
+This specification must satisfy the client interface defined in ICS 2.
+
+## Technical Specification
+
+This specification depends on correct instantiation of the [GRANDPA finality gadget](https://github.com/w3f/consensus/blob/master/pdf/grandpa.pdf) and its light client algorithm.
+
+### Client state
+
+The GRANDPA client state tracks latest height and a possible frozen height.
+
+```typescript
+interface ClientState {
+ latestHeight: uint64
+ frozenHeight: Maybe
+}
+```
+
+### Authority set
+
+A set of authorities for GRANDPA.
+
+```typescript
+interface AuthoritySet {
+ // this is incremented every time the set changes
+ setId: uint64
+ authorities: List>
+}
+```
+
+### Consensus state
+
+The GRANDPA client tracks authority set and commitment root for all previously verified consensus states.
+
+```typescript
+interface ConsensusState {
+ authoritySet: AuthoritySet
+ commitmentRoot: []byte
+}
+```
+
+### Headers
+
+The GRANDPA client headers include the height, the commitment root, a justification of block and authority set.
+(In fact, here is a proof of authority set rather than the authority set itself, but we can use a fixed key to verify
+the proof and extract the real set, the details are ignored here)
+
+```typescript
+interface Header {
+ height: uint64
+ commitmentRoot: []byte
+ justification: Justification
+ authoritySet: AuthoritySet
+}
+```
+
+### Justification
+
+A GRANDPA justification for block finality, it includes a commit message and an ancestry proof including all headers routing all precommit target blocks to the commit target block.
+For example, the latest blocks are A - B - C - D - E - F, where A is the last finalised block, F is the point where a majority for vote (they may on B, C, D, E, F) can be collected. Then the proof needs to include all headers from F back to A.
+
+```typescript
+interface Justification {
+ round: uint64
+ commit: Commit
+ votesAncestries: []Header
+}
+```
+
+### Commit
+
+A commit message which is an aggregate of signed precommits.
+
+```typescript
+interface Commit {
+ precommits: []SignedPrecommit
+}
+
+interface SignedPrecommit {
+ targetHash: Hash
+ signature: Signature
+ id: AuthorityId
+}
+```
+
+### Misbehaviour
+
+The `Misbehaviour` type is used for detecting misbehaviour and freezing the client - to prevent further packet flow - if applicable.
+GRANDPA client `Misbehaviour` consists of two headers at the same height both of which the light client would have considered valid.
+
+```typescript
+interface Misbehaviour {
+ fromHeight: uint64
+ h1: Header
+ h2: Header
+}
+```
+
+### Client initialisation
+
+GRANDPA client initialisation requires a (subjectively chosen) latest consensus state, including the full authority set.
+
+```typescript
+function initialise(identifier: Identifier, height: uint64, consensusState: ConsensusState): ClientState {
+ set("clients/{identifier}/consensusStates/{height}", consensusState)
+ return ClientState{
+ latestHeight: height,
+ frozenHeight: null,
+ }
+}
+```
+
+The GRANDPA client `latestClientHeight` function returns the latest stored height, which is updated every time a new (more recent) header is validated.
+
+```typescript
+function latestClientHeight(clientState: ClientState): uint64 {
+ return clientState.latestHeight
+}
+```
+
+### Validity predicate
+
+GRANDPA client validity checking verifies a header is signed by the current authority set and verifies the authority set proof to determine if there is an expected change to the authority set. If the provided header is valid, the client state is updated & the newly verified commitment written to the store.
+
+```typescript
+function checkValidityAndUpdateState(
+ clientState: ClientState,
+ header: Header) {
+ // assert header height is newer than any we know
+ assert(header.height > clientState.latestHeight)
+ consensusState = get("clients/{identifier}/consensusStates/{clientState.latestHeight}")
+ // verify that the provided header is valid
+ assert(verify(consensusState.authoritySet, header))
+ // update latest height
+ clientState.latestHeight = header.height
+ // create recorded consensus state, save it
+ consensusState = ConsensusState{header.authoritySet, header.commitmentRoot}
+ set("clients/{identifier}/consensusStates/{header.height}", consensusState)
+ // save the client
+ set("clients/{identifier}", clientState)
+}
+
+function verify(
+ authoritySet: AuthoritySet,
+ header: Header): boolean {
+ let visitedHashes: Hash[]
+ for (const signedPrecommit of Header.justification.commit.precommits) {
+ if (checkSignature(authoritySet, signedPrecommit)) {
+ visitedHashes.push(signedPrecommit.targetHash)
+ }
+ }
+ return visitedHashes.equals(Header.justification.votesAncestries.map(hash))
+}
+```
+
+### Misbehaviour predicate
+
+GRANDPA client misbehaviour checking determines whether or not two conflicting headers at the same height would have convinced the light client.
+
+```typescript
+function checkMisbehaviourAndUpdateState(
+ clientState: ClientState,
+ misbehaviour: Misbehaviour) {
+ // assert that the heights are the same
+ assert(misbehaviour.h1.height === misbehaviour.h2.height)
+ // assert that the commitments are different
+ assert(misbehaviour.h1.commitmentRoot !== misbehaviour.h2.commitmentRoot)
+ // fetch the previously verified commitment root & authority set
+ consensusState = get("clients/{identifier}/consensusStates/{misbehaviour.fromHeight}")
+ // check if the light client "would have been fooled"
+ assert(
+ verify(consensusState.authoritySet, misbehaviour.h1) &&
+ verify(consensusState.authoritySet, misbehaviour.h2)
+ )
+ // set the frozen height
+ clientState.frozenHeight = min(clientState.frozenHeight, misbehaviour.h1.height) // which is same as h2.height
+ // save the client
+ set("clients/{identifier}", clientState)
+}
+```
+
+### State verification functions
+
+GRANDPA client state verification functions check a Merkle proof against a previously validated commitment root.
+
+```typescript
+function verifyClientConsensusState(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ clientIdentifier: Identifier,
+ consensusStateHeight: uint64,
+ consensusState: ConsensusState) {
+ path = applyPrefix(prefix, "clients/{clientIdentifier}/consensusState/{consensusStateHeight}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the provided consensus state has been stored
+ assert(root.verifyMembership(path, consensusState, proof))
+}
+
+function verifyConnectionState(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ connectionIdentifier: Identifier,
+ connectionEnd: ConnectionEnd) {
+ path = applyPrefix(prefix, "connections/{connectionIdentifier}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the provided connection end has been stored
+ assert(root.verifyMembership(path, connectionEnd, proof))
+}
+
+function verifyChannelState(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ channelEnd: ChannelEnd) {
+ path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the provided channel end has been stored
+ assert(root.verifyMembership(path, channelEnd, proof))
+}
+
+function verifyPacketData(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ data: bytes) {
+ path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/packets/{sequence}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the provided commitment has been stored
+ assert(root.verifyMembership(path, hash(data), proof))
+}
+
+function verifyPacketAcknowledgement(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ acknowledgement: bytes) {
+ path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the provided acknowledgement has been stored
+ assert(root.verifyMembership(path, hash(acknowledgement), proof))
+}
+
+function verifyPacketReceiptAbsence(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64) {
+ path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/acknowledgements/{sequence}")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that no acknowledgement has been stored
+ assert(root.verifyNonMembership(path, proof))
+}
+
+function verifyNextSequenceRecv(
+ clientState: ClientState,
+ height: uint64,
+ prefix: CommitmentPrefix,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ nextSequenceRecv: uint64) {
+ path = applyPrefix(prefix, "ports/{portIdentifier}/channels/{channelIdentifier}/nextSequenceRecv")
+ // check that the client is at a sufficient height
+ assert(clientState.latestHeight >= height)
+ // check that the client is unfrozen or frozen at a higher height
+ assert(clientState.frozenHeight === null || clientState.frozenHeight > height)
+ // fetch the previously verified commitment root & verify membership
+ root = get("clients/{identifier}/consensusStates/{height}")
+ // verify that the nextSequenceRecv is as claimed
+ assert(root.verifyMembership(path, nextSequenceRecv, proof))
+}
+```
+
+### Properties & Invariants
+
+Correctness guarantees as provided by the GRANDPA light client algorithm.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Not applicable. Alterations to the client verification algorithm will require a new client standard.
+
+## Example Implementations
+
+None yet.
+
+## History
+
+March 15, 2020 - Initial version
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-002-client-semantics/README.mdx b/ibc/next/spec/core/ics-002-client-semantics/README.mdx
new file mode 100644
index 00000000..0b8e52ed
--- /dev/null
+++ b/ibc/next/spec/core/ics-002-client-semantics/README.mdx
@@ -0,0 +1,670 @@
+---
+ics: 2
+title: Client Semantics
+stage: draft
+category: IBC/TAO
+kind: interface
+requires: [23, 24]
+required_by: [3]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Juwoon Yun
+ email: joon@tendermint.com
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+ - name: Aditya Sripal
+ email: aditya@interchain.io
+created: 2019-02-25
+modified: 2022-08-04
+---
+
+
+## Synopsis
+
+This standard specifies the properties that consensus algorithms of state machines implementing the inter-blockchain
+communication (IBC) protocol are required to satisfy.
+These properties are necessary for efficient and safe verification in the higher-level protocol abstractions.
+The algorithm utilised in IBC to verify the state updates of a remote state machine is referred to as a *validity predicate*.
+Pairing a validity predicate with a trusted state (i.e., a state that the verifier assumes to be correct),
+implements the functionality of a *light client* (often shortened to *client*) for a remote state machine on the host state machine.
+In addition to state update verification, every light client is able to detect consensus misbehaviours through a *misbehaviour predicate*.
+
+Beyond the properties described in this specification, IBC does not impose any requirements on
+the internal operation of the state machines and their consensus algorithms.
+A state machine may consist of a single process signing operations with a private key (the so-called "solo machine"), a quorum of processes signing in unison,
+many processes operating a Byzantine fault-tolerant consensus algorithm (e.g., Tendermint), or other configurations yet to be invented
+— from the perspective of IBC, a state machine is defined entirely by its light client validation and misbehaviour detection logic.
+
+This standard also specifies how the light client's functionality is registered and how its data is stored and updated by the IBC protocol.
+The stored client instances can be introspected by a third party actor,
+such as a user inspecting the state of the state machine and deciding whether or not to send an IBC packet.
+
+### Motivation
+
+In the IBC protocol, an actor, which may be an end user, an off-chain process, or a module on a state machine,
+needs to be able to verify updates to the state of another state machine (i.e., the *remote state machine*).
+This entails accepting *only* the state updates that were agreed upon by the remote state machine's consensus algorithm.
+A light client of the remote state machine is the algorithm that enables the actor to verify state updates of that state machine.
+Note that light clients will generally not include validation of the entire state transition logic
+(as that would be equivalent to simply executing the other state machine), but may
+elect to validate parts of state transitions in particular cases.
+This standard formalises the light client model and requirements.
+As a result, the IBC protocol can easily be integrated with new state machines running new consensus algorithms,
+as long as the necessary light client algorithms fulfilling the listed requirements are provided.
+
+The IBC protocol can be used to interact with probabilistic-finality consensus algorithms.
+In such cases, different validity predicates may be required by different applications. For probabilistic-finality consensus, a validity predicate is defined by a finality threshold (e.g., the threshold defines how many block needs to be on top of a block in order to consider it finalized).
+As a result, clients could act as *thresholding views* of other clients:
+One *write-only* client could be used to store state updates (without the ability to verify them),
+while many *read-only* clients with different finality thresholds (confirmation depths after which
+state updates are considered final) are used to verify state updates.
+
+The client protocol should also support third-party introduction.
+For example, if `A`, `B`, and `C` are three state machines, with
+Alice a module on `A`, Bob a module on `B`, and Carol a module on `C`, such that
+Alice knows both Bob and Carol, but Bob knows only Alice and not Carol,
+then Alice can utilise an existing channel to Bob to communicate the canonically-serialisable
+validity predicate for Carol. Bob can then use this validity predicate to open a connection and channel
+so that Bob and Carol can talk directly.
+If necessary, Alice may also communicate to Carol the validity predicate for Bob, prior to Bob's
+connection attempt, so that Carol knows to accept the incoming request.
+
+Client interfaces should also be constructed so that custom validation logic can be provided safely
+to define a custom client at runtime, as long as the underlying state machine can provide an
+appropriate gas metering mechanism to charge for compute and storage. On a host state machine
+which supports WASM execution, for example, the validity predicate and misbehaviour predicate
+could be provided as executable WASM functions when the client instance is created.
+
+### Definitions
+
+- `get`, `set`, `Path`, and `Identifier` are as defined in [ICS 24](../ics-024-host-requirements).
+
+- `Consensus` is a state update generating algorithm. It takes the previous state of a state machine together
+ with a set of messages (i.e., state machine transactions) and generates a valid state update of the state machine.
+ Every state machine MUST have a `Consensus` that generates a unique, ordered list of state updates
+ starting from a genesis state.
+
+ This specification expects that the state updates generated by `Consensus`
+ satisfy the following properties:
+ - Every state update MUST NOT have more than one direct successor in the list of state updates.
+ In other words, the state machine MUST guarantee *finality* and *safety*.
+ - Every state update MUST eventually have a successor in the list of state updates.
+ In other words, the state machine MUST guarantee *liveness*.
+ - Every state update MUST be valid (i.e., valid state transitions).
+ In other words, `Consensus` MUST be *honest*,
+ e.g., in the case `Consensus` is a Byzantine fault-tolerant consensus algorithm,
+ such as Tendermint, less than a third of block producers MAY be Byzantine.
+
+ Unless the state machine satisfies all of the above properties, the IBC protocol
+may not work as intended, e.g., users' assets might be stolen. Note that specific client
+types may require additional properties.
+
+- `Height` specifies the order of the state updates of a state machine, e.g., a sequence number.
+ This entails that each state update is mapped to a `Height`.
+
+- `CommitmentRoot` is as defined in [ICS 23](../ics-023-vector-commitments).
+ It provides an efficient way for higher-level protocol abstractions to verify whether
+ a particular state transition has occurred on the remote state machine, i.e.,
+ it enables proofs of inclusion or non-inclusion of particular values at particular paths
+ in the state of the remote state machine at particular `Height`s.
+
+- `ClientMessage` is an arbitrary message defined by the client type that relayers can submit in order to update the client.
+ The ClientMessage may be intended as a regular update which may add new consensus state for proof verification, or it may contain
+ misbehaviour which should freeze the client.
+
+- `ValidityPredicate` is a function that validates a ClientMessage sent by a relayer in order to update the client.
+ Using the `ValidityPredicate` SHOULD be more computationally efficient than executing `Consensus`.
+
+- `ConsensusState` is the *trusted view* of the state of a state machine at a particular `Height`.
+ It MUST contain sufficient information to enable the `ValidityPredicate` to validate state updates,
+ which can then be used to generate new `ConsensusState`s.
+ It MUST be serialisable in a canonical fashion so that remote parties, such as remote state machines,
+ can check whether a particular `ConsensusState` was stored by a particular state machine.
+ It MUST be introspectable by the state machine whose view it represents,
+ i.e., a state machine can look up its own `ConsensusState`s at past `Height`s.
+
+- `ClientState` is the state of a client. It MUST expose an interface to higher-level protocol abstractions,
+ e.g., functions to verify proofs of the existence of particular values at particular paths at particular `Height`s.
+
+- `MisbehaviourPredicate` is a function that checks whether the rules of `Consensus` were broken,
+ in which case the client MUST be *frozen*, i.e., no subsequent `ConsensusState`s can be generated.
+
+- `Misbehaviour` is the proof needed by the `MisbehaviourPredicate` to determine whether
+ a violation of the consensus protocol occurred. For example, in the case the state machine
+ is a blockchain, a `Misbehaviour` might consist of two signed block headers with
+ different `CommitmentRoot`s, but the same `Height`.
+
+### Desired Properties
+
+Light clients MUST provide state verification functions that provide a secure way
+to verify the state of the remote state machines using the existing `ConsensusState`s.
+These state verification functions enable higher-level protocol abstractions to
+verify sub-components of the state of the remote state machines.
+
+`ValidityPredicate`s MUST reflect the behaviour of the remote state machine and its `Consensus`, i.e.,
+`ValidityPredicate`s accept *only* state updates that contain state updates generated by
+the `Consensus` of the remote state machine.
+
+In case of misbehavior, the behaviour of the `ValidityPredicate` might differ from the behaviour of
+the remote state machine and its `Consensus` (since clients do not execute the `Consensus` of the
+remote state machine). In this case, a `Misbehaviour` SHOULD be submitted to the host state machine,
+which would result in the client being frozen and higher-level intervention being necessary.
+
+## Technical Specification
+
+This specification outlines what each *client type* must define. A client type is a set of definitions
+of the data structures, initialisation logic, validity predicate, and misbehaviour predicate required
+to operate a light client. State machines implementing the IBC protocol can support any number of client
+types, and each client type can be instantiated with different initial consensus states in order to track
+different consensus instances. In order to establish a connection between two state machines (see [ICS 3](../ics-003-connection-semantics)),
+the state machines must each support the client type corresponding to the other state machine's consensus algorithm.
+
+Specific client types shall be defined in later versions of this specification and a canonical list shall exist in this repository.
+State machines implementing the IBC protocol are expected to respect these client types, although they may elect to support only a subset.
+
+### Data Structures
+
+#### `Height`
+
+`Height` is an opaque data structure defined by a client type.
+It must form a partially ordered set & provide operations for comparison.
+
+```typescript
+type Height
+```
+
+```typescript
+enum Ord {
+ LT
+ EQ
+ GT
+}
+
+type compare = (h1: Height, h2: Height) => Ord
+```
+
+A height is either `LT` (less than), `EQ` (equal to), or `GT` (greater than) another height.
+
+`>=`, `>`, `===`, `<`, `<=` are defined through the rest of this specification as aliases to `compare`.
+
+There must also be a zero-element for a height type, referred to as `0`, which is less than all non-zero heights.
+
+#### `ConsensusState`
+
+`ConsensusState` is an opaque data structure defined by a client type, used by the validity predicate to
+verify new commits & state roots. Likely the structure will contain the last commit produced by
+the consensus process, including signatures and validator set metadata.
+
+`ConsensusState` MUST be generated from an instance of `Consensus`, which assigns unique heights
+for each `ConsensusState` (such that each height has exactly one associated consensus state).
+Two `ConsensusState`s on the same chain SHOULD NOT have the same height if they do not have
+equal commitment roots. Such an event is called an "equivocation" and MUST be classified
+as misbehaviour. Should one occur, a proof should be generated and submitted so that the client can be frozen
+and previous state roots invalidated as necessary.
+
+The `ConsensusState` of a chain MUST have a canonical serialisation, so that other chains can check
+that a stored consensus state is equal to another (see [ICS 24](../ics-024-host-requirements) for the keyspace table).
+
+```typescript
+type ConsensusState = bytes
+```
+
+The `ConsensusState` MUST be stored under a particular key, defined below, so that other chains can verify that a particular consensus state has been stored.
+
+The `ConsensusState` MUST define a `getTimestamp()` method which returns the timestamp associated with that consensus state:
+
+```typescript
+type getTimestamp = ConsensusState => uint64
+```
+
+#### `ClientState`
+
+`ClientState` is an opaque data structure defined by a client type.
+It may keep arbitrary internal state to track verified roots and past misbehaviours.
+
+Light clients are representation-opaque — different consensus algorithms can define different light client update algorithms —
+but they must expose this common set of query functions to the IBC handler.
+
+```typescript
+type ClientState = bytes
+```
+
+Client types MUST define a method to initialise a client state with the provided client identifier, client state and consensus state, writing to internal state as appropriate.
+
+```typescript
+type initialise = (identifier: Identifier, clientState: ClientState, consensusState: ConsensusState) => Void
+```
+
+Client types MUST define a method to fetch the current height (height of the most recent validated state update).
+
+```typescript
+type latestClientHeight = (
+ clientState: ClientState)
+ => Height
+```
+
+Client types MUST define a method on the client state to fetch the timestamp at a given height
+
+```typescript
+type getTimestampAtHeight = (
+ clientState: ClientState,
+ height: Height
+) => uint64
+```
+
+#### `ClientMessage`
+
+A `ClientMessage` is an opaque data structure defined by a client type which provides information to update the client.
+`ClientMessage`s can be submitted to an associated client to add new `ConsensusState`(s) and/or update the `ClientState`. They likely contain a height, a proof, a commitment root, and possibly updates to the validity predicate.
+
+```typescript
+type ClientMessage = bytes
+```
+
+### Store paths
+
+Client state paths are stored under a unique client identifier.
+
+```typescript
+function clientStatePath(id: Identifier): Path {
+ return "clients/{id}/clientState"
+}
+```
+
+Consensus state paths are stored under a unique combination of client identifier and height:
+
+```typescript
+function consensusStatePath(id: Identifier, height: Height): Path {
+ return "clients/{id}/consensusStates/{height}"
+}
+```
+
+#### Validity predicate
+
+A validity predicate is an opaque function defined by a client type to verify `ClientMessage`s depending on the current `ConsensusState`.
+Using the validity predicate SHOULD be far more computationally efficient than replaying the full consensus algorithm
+for the given parent `ClientMessage` and the list of network messages.
+
+The validity predicate is defined as:
+
+```typescript
+type verifyClientMessage = (ClientMessage) => Void
+```
+
+`verifyClientMessage` MUST throw an exception if the provided ClientMessage was not valid.
+
+#### Misbehaviour predicate
+
+A misbehaviour predicate is an opaque function defined by a client type, used to check if a ClientMessage
+constitutes a violation of the consensus protocol. For example, if the state machine is a blockchain, this might be two signed headers
+with different state roots but the same height, a signed header containing invalid
+state transitions, or other proof of malfeasance as defined by the consensus algorithm.
+
+The misbehaviour predicate is defined as
+
+```typescript
+type checkForMisbehaviour = (ClientMessage) => bool
+```
+
+`checkForMisbehaviour` MUST throw an exception if the provided proof of misbehaviour was not valid.
+
+#### Update state
+
+Function `updateState` is an opaque function defined by a client type that will update the client given a verified `ClientMessage`. Note that this function is intended for **non-misbehaviour** `ClientMessage`s.
+
+```typescript
+type updateState = (ClientMessage) => Void
+```
+
+`verifyClientMessage` must be called before this function, and `checkForMisbehaviour` must return false before this function is called.
+
+The client MUST also mutate internal state to store
+now-finalised consensus roots and update any necessary signature authority tracking (e.g.
+changes to the validator set) for future calls to the validity predicate.
+
+Clients MAY have time-sensitive validity predicates, such that if no ClientMessage is provided for a period of time
+(e.g. an unbonding period of three weeks) it will no longer be possible to update the client, i.e., the client is being frozen.
+In this case, a permissioned entity such as a chain governance system or trusted multi-signature MAY be allowed
+to intervene to unfreeze a frozen client & provide a new correct ClientMessage.
+
+#### Update state on misbehaviour
+
+Function `updateStateOnMisbehaviour` is an opaque function defined by a client type that will update the client upon receiving a verified `ClientMessage` that is valid misbehaviour.
+
+```typescript
+type updateStateOnMisbehaviour = (ClientMessage) => Void
+```
+
+`verifyClientMessage` must be called before this function, and `checkForMisbehaviour` must return `true` before this function is called.
+
+The client MUST also mutate internal state to mark appropriate heights which
+were previously considered valid as invalid, according to the nature of the misbehaviour.
+
+Once misbehaviour is detected, clients SHOULD be frozen so that no future updates can be submitted.
+A permissioned entity such as a chain governance system or trusted multi-signature MAY be allowed
+to intervene to unfreeze a frozen client & provide a new correct ClientMessage which updates the client to a valid state.
+
+#### `CommitmentProof`
+
+`CommitmentProof` is an opaque data structure defined by a client type in accordance with [ICS 23](../ics-023-vector-commitments).
+It is utilised to verify presence or absence of a particular key/value pair in state
+at a particular finalised height (necessarily associated with a particular commitment root).
+
+### State verification
+
+Client types must define functions to authenticate internal state of the state machine which the client tracks.
+Internal implementation details may differ (for example, a loopback client could simply read directly from the state and require no proofs).
+
+- The `delayPeriodTime` is passed to the verification functions for packet-related proofs in order to allow packets to specify a period of time which must pass after a consensus state is added before it can be used for packet-related verification.
+- The `delayPeriodBlocks` is passed to the verification functions for packet-related proofs in order to allow packets to specify a period of blocks which must pass after a consensus state is added before it can be used for packet-related verification.
+
+`verifyMembership` is a generic proof verification method which verifies a proof of the existence of a value at a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful.
+The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 24](../ics-024-host-requirements/README#path-space)). If the caller desires a particular delay period to be enforced,
+then it can pass in a non-zero `delayPeriodTime` or `delayPeriodBlocks`. If a delay period is not necessary, the caller must pass in 0 for `delayPeriodTime` and `delayPeriodBlocks`,
+and the client will not enforce any delay period for verification.
+
+```typescript
+type verifyMembership = (
+ clientState: ClientState,
+ height: Height,
+ delayPeriodTime: uint64,
+ delayPeriodBlocks: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath,
+ value: bytes)
+ => Error
+```
+
+`verifyNonMembership` is a generic proof verification method which verifies a proof of absence of a given `CommitmentPath` at the specified height. It MUST return an error if the verification is not successful.
+The caller is expected to construct the full `CommitmentPath` from a `CommitmentPrefix` and a standardized path (as defined in [ICS 24](../ics-024-host-requirements/README#path-space)). If the caller desires a particular delay period to be enforced,
+then it can pass in a non-zero `delayPeriodTime` or `delayPeriodBlocks`. If a delay period is not necessary, the caller must pass in 0 for `delayPeriodTime` and `delayPeriodBlocks`,
+and the client will not enforce any delay period for verification.
+
+Since the verification method is designed to give complete control to client implementations, clients can support chains that do not provide absence proofs by verifying the existence of a non-empty sentinel `ABSENCE` value. Thus in these special cases, the proof provided will be an ICS-23 Existence proof, and the client will verify that the `ABSENCE` value is stored under the given path for the given height.
+
+```typescript
+type verifyNonMembership = (
+ clientState: ClientState,
+ height: Height,
+ delayPeriodTime: uint64,
+ delayPeriodBlocks: uint64,
+ proof: CommitmentProof,
+ path: CommitmentPath)
+ => Error
+```
+
+### Query interface
+
+#### Chain queries
+
+These query endpoints are assumed to be exposed over HTTP or an equivalent RPC API by nodes of the chain associated with a particular client.
+
+`queryUpdate` MUST be defined by the chain which is validated by a particular client, and should allow for retrieval of clientMessage for a given height. This endpoint is assumed to be untrusted.
+
+```typescript
+type queryUpdate = (height: Height) => ClientMessage
+```
+
+`queryChainConsensusState` MAY be defined by the chain which is validated by a particular client, to allow for the retrieval of the current consensus state which can be used to construct a new client.
+When used in this fashion, the returned `ConsensusState` MUST be manually confirmed by the querying entity, since it is subjective. This endpoint is assumed to be untrusted. The precise nature of the
+`ConsensusState` may vary per client type.
+
+```typescript
+type queryChainConsensusState = (height: Height) => ConsensusState
+```
+
+Note that retrieval of past consensus states by height (as opposed to just the current consensus state) is convenient but not required.
+
+`queryChainConsensusState` MAY also return other data necessary to create clients, such as the "unbonding period" for certain proof-of-stake security models. This data MUST also be verified by the querying entity.
+
+#### On-chain state queries
+
+This specification defines a single function to query the state of a client by-identifier.
+
+```typescript
+function queryClientState(identifier: Identifier): ClientState {
+ return provableStore.get(clientStatePath(identifier))
+}
+```
+
+The `ClientState` type SHOULD expose its latest verified height (from which the consensus state can then be retrieved using `queryConsensusState` if desired).
+
+```typescript
+type latestHeight = (state: ClientState) => Height
+```
+
+Client types SHOULD define the following standardised query functions in order to allow relayers & other off-chain entities to interface with on-chain state in a standard API.
+
+`queryConsensusState` allows stored consensus states to be retrieved by height.
+
+```typescript
+type queryConsensusState = (
+ identifier: Identifier,
+ height: Height,
+) => ConsensusState
+```
+
+#### Proof construction
+
+Each client type SHOULD define functions to allow relayers to construct the proofs required by the client's state verification algorithms. These may take different forms depending on the client type.
+For example, Tendermint client proofs may be returned along with key-value data from store queries, and solo client proofs may need to be constructed interactively on the solo state machine in question (since the user will need to sign the message).
+These functions may constitute external queries over RPC to a full node as well as local computation or verification.
+
+```typescript
+type queryAndProveClientConsensusState = (
+ clientIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix,
+ consensusStateHeight: Height) => ConsensusState, Proof
+
+type queryAndProveConnectionState = (
+ connectionIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix) => ConnectionEnd, Proof
+
+type queryAndProveChannelState = (
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix) => ChannelEnd, Proof
+
+type queryAndProvePacketData = (
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix,
+ sequence: uint64) => []byte, Proof
+
+type queryAndProvePacketAcknowledgement = (
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix,
+ sequence: uint64) => []byte, Proof
+
+type queryAndProvePacketAcknowledgementAbsence = (
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix,
+ sequence: uint64) => Proof
+
+type queryAndProveNextSequenceRecv = (
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ height: Height,
+ prefix: CommitmentPrefix) => uint64, Proof
+```
+
+#### Implementation strategies
+
+##### Loopback
+
+A loopback client of a local state machine merely reads from the local state, to which it must have access.
+
+##### Simple signatures
+
+A client of a solo state machine with a known public key checks signatures on messages sent by that local state machine,
+which are provided as the `Proof` parameter. The `height` parameter can be used as a replay protection nonce.
+
+Multi-signature or threshold signature schemes can also be used in such a fashion.
+
+##### Proxy clients
+
+Proxy clients verify another (proxy) state machine's verification of the target state machine, by including in the
+proof first a proof of the client state on the proxy state machine, and then a secondary proof of the sub-state of
+the target state machine with respect to the client state on the proxy state machine. This allows the proxy client to
+avoid storing and tracking the consensus state of the target state machine itself, at the cost of adding
+security assumptions of proxy state machine correctness.
+
+##### Merklized state trees
+
+For clients of state machines with Merklized state trees, these functions can be implemented by calling the [ICS-23](../ics-023-vector-commitments/README.md) `verifyMembership` or `verifyNonMembership` methods, using a verified Merkle
+root stored in the `ClientState`, to verify presence or absence of particular key/value pairs in state at particular heights in accordance with [ICS 23](../ics-023-vector-commitments).
+
+```typescript
+type verifyMembership = (ClientState, Height, CommitmentProof, Path, Value) => boolean
+```
+
+```typescript
+type verifyNonMembership = (ClientState, Height, CommitmentProof, Path) => boolean
+```
+
+### Sub-protocols
+
+IBC handlers MUST implement the functions defined below.
+
+#### Identifier validation
+
+Clients are stored under a unique `Identifier` prefix.
+This ICS does not require that client identifiers be generated in a particular manner, only that they be unique.
+However, it is possible to restrict the space of `Identifier`s if required.
+The validation function `validateClientIdentifier` MAY be provided.
+
+```typescript
+type validateClientIdentifier = (id: Identifier) => boolean
+```
+
+If not provided, the default `validateClientIdentifier` will always return `true`.
+
+##### Utilising past roots
+
+To avoid race conditions between client updates (which change the state root) and proof-carrying
+transactions in handshakes or packet receipt, many IBC handler functions allow the caller to specify
+a particular past root to reference, which is looked up by height. IBC handler functions which do this
+must ensure that they also perform any requisite checks on the height passed in by the caller to ensure
+logical correctness.
+
+#### Create
+
+Calling `createClient` with the client state and initial consensus state creates a new client.
+
+```typescript
+function createClient(clientState: clientState, consensusState: ConsensusState) {
+ // implementations may define a identifier generation function
+ identifier = generateClientIdentifier()
+ abortTransactionUnless(provableStore.get(clientStatePath(identifier)) === null)
+ initialise(identifier, clientState, consensusState)
+}
+```
+
+#### Query
+
+Client consensus state and client internal state can be queried by identifier, but
+the specific paths which must be queried are defined by each client type.
+
+#### Update
+
+Updating a client is done by submitting a new `ClientMessage`. The `Identifier` is used to point to the
+stored `ClientState` that the logic will update. When a new `ClientMessage` is verified with
+the stored `ClientState`'s validity predicate and `ConsensusState`, the client MUST
+update its internal state accordingly, possibly finalising commitment roots and
+updating the signature authority logic in the stored consensus state.
+
+If a client can no longer be updated (if, for example, the trusting period has passed),
+it will no longer be possible to send any packets over connections & channels associated
+with that client, or timeout any packets in-flight (since the height & timestamp on the
+destination chain can no longer be verified). Manual intervention must take place to
+reset the client state or migrate the connections & channels to another client. This
+cannot safely be done completely automatically, but chains implementing IBC could elect
+to allow governance mechanisms to perform these actions
+(perhaps even per-client/connection/channel in a multi-sig or contract).
+
+```typescript
+function updateClient(
+ id: Identifier,
+ clientMessage: ClientMessage) {
+ // get clientState from store with id
+ clientState = provableStore.get(clientStatePath(id))
+ abortTransactionUnless(clientState !== null)
+
+ verifyClientMessage(clientMessage)
+
+ foundMisbehaviour := clientState.CheckForMisbehaviour(clientMessage)
+ if foundMisbehaviour {
+ updateStateOnMisbehaviour(clientMessage)
+ // emit misbehaviour event
+ }
+ else {
+ updateState(clientMessage) // expects no-op on duplicate clientMessage
+ // emit update event
+ }
+}
+```
+
+#### Misbehaviour
+
+A relayer may alert the client to the misbehaviour directly, possibly invalidating
+previously valid state roots & preventing future updates.
+
+```typescript
+function submitMisbehaviourToClient(
+ id: Identifier,
+ clientMessage: ClientMessage) {
+ clientState = provableStore.get(clientStatePath(id))
+ abortTransactionUnless(clientState !== null)
+ // authenticate client message
+ verifyClientMessage(clientMessage)
+ // check that client message is valid instance of misbehaviour
+ abortTransactionUnless(clientState.checkForMisbehaviour(clientMessage))
+ // update state based on misbehaviour
+ updateStateOnMisbehaviour(misbehaviour)
+}
+```
+
+### Properties & Invariants
+
+- Client identifiers are immutable & first-come-first-serve. Clients cannot be deleted (allowing deletion would potentially allow future replay of past packets if identifiers were re-used).
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+New client types can be added by IBC implementations at-will as long as they conform to this interface.
+
+## Example Implementations
+
+Please see the ibc-go implementations of light clients for examples of how to implement your own: (https://github.com/cosmos/ibc-go/blob/main/modules/light-clients).
+
+## History
+
+Mar 5, 2019 - Initial draft finished and submitted as a PR
+
+May 29, 2019 - Various revisions, notably multiple commitment-roots
+
+Aug 15, 2019 - Major rework for clarity around client interface
+
+Jan 13, 2020 - Revisions for client type separation & path alterations
+
+Jan 26, 2020 - Addition of query interface
+
+Jul 27, 2022 - Addition of `verifyClientState` function, and move `ClientState` to the `provableStore`
+
+August 4, 2022 - Changes to ClientState interface and associated handler to align with changes in 02-client-refactor ADR: (https://github.com/cosmos/ibc-go/pull/1871)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-003-connection-semantics/README.mdx b/ibc/next/spec/core/ics-003-connection-semantics/README.mdx
new file mode 100644
index 00000000..e8475731
--- /dev/null
+++ b/ibc/next/spec/core/ics-003-connection-semantics/README.mdx
@@ -0,0 +1,610 @@
+---
+ics: 3
+title: Connection Semantics
+stage: draft
+category: IBC/TAO
+kind: instantiation
+requires: [2, 24]
+required_by: [4, 25]
+version_compatibility:
+ - ibc-go v9.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+ - name: Juwoon Yun
+ email: joon@tendermint.com
+created: 2019-03-07
+modified: 2024-07-23
+---
+
+## Synopsis
+
+This standards document describes the abstraction of an IBC *connection*: two stateful objects (*connection ends*) on two separate chains, each associated with a light client of the other chain, which together facilitate cross-chain sub-state verification and packet association (through channels). A protocol for safely establishing a connection between two chains is described.
+
+### Motivation
+
+The core IBC protocol provides *authorisation* and *ordering* semantics for packets: guarantees, respectively, that packets have been committed on the sending blockchain (and according state transitions executed, such as escrowing tokens), and that they have been committed exactly once in a particular order and can be delivered exactly once in that same order. The *connection* abstraction specified in this standard, in conjunction with the *client* abstraction specified in [ICS 2](../ics-002-client-semantics), defines the *authorisation* semantics of IBC. Ordering semantics are described in [ICS 4](../ics-004-channel-and-packet-semantics)).
+
+### Definitions
+
+Client-related types & functions are as defined in [ICS 2](../ics-002-client-semantics).
+
+Channel and packet-related functions are as defined in [ICS 4](../ics-004-channel-and-packet-semantics).
+
+Commitment proof related types & functions are defined in [ICS 23](../ics-023-vector-commitments)
+
+`Identifier` and other host state machine requirements are as defined in [ICS 24](../ics-024-host-requirements). The identifier is not necessarily intended to be a human-readable name (and likely should not be, to discourage squatting or racing for identifiers).
+
+The opening handshake protocol allows each chain to verify the identifier used to reference the connection on the other chain, enabling modules on each chain to reason about the reference on the other chain.
+
+An *actor*, as referred to in this specification, is an entity capable of executing datagrams who is paying for computation / storage (via gas or a similar mechanism) but is otherwise untrusted. Possible actors include:
+
+- End users signing with an account key
+- On-chain smart contracts acting autonomously or in response to another transaction
+- On-chain modules acting in response to another transaction or in a scheduled manner
+
+### Desired Properties
+
+- Implementing blockchains should be able to safely allow untrusted actors to open and update connections.
+
+#### Pre-Establishment
+
+Prior to connection establishment:
+
+- No further IBC sub-protocols should operate, since cross-chain sub-states cannot be verified.
+- The initiating actor (who creates the connection) must be able to specify an initial consensus state for the chain to connect to and an initial consensus state for the connecting chain (implicitly, e.g. by sending the transaction).
+
+#### During Handshake
+
+Once a negotiation handshake has begun:
+
+- Only the appropriate handshake datagrams can be executed in order.
+- No third chain can masquerade as one of the two handshaking chains
+
+#### Post-Establishment
+
+Once a negotiation handshake has completed:
+
+- The created connection objects on both chains contain the consensus states specified by the initiating actor.
+- No other connection objects can be maliciously created on other chains by replaying datagrams.
+
+## Technical Specification
+
+### Data Structures
+
+This ICS defines the `ConnectionState` and `ConnectionEnd` types:
+
+```typescript
+enum ConnectionState {
+ INIT,
+ TRYOPEN,
+ OPEN,
+}
+```
+
+```typescript
+interface ConnectionEnd {
+ state: ConnectionState
+ counterpartyConnectionIdentifier: Identifier
+ counterpartyPrefix: CommitmentPrefix
+ clientIdentifier: Identifier
+ counterpartyClientIdentifier: Identifier
+ version: string | []string
+ delayPeriodTime: uint64
+ delayPeriodBlocks: uint64
+}
+```
+
+- The `state` field describes the current state of the connection end.
+- The `counterpartyConnectionIdentifier` field identifies the connection end on the counterparty chain associated with this connection.
+- The `counterpartyPrefix` field contains the prefix used for state verification on the counterparty chain associated with this connection.
+ Chains should expose an endpoint to allow relayers to query the connection prefix.
+ If not specified, a default `counterpartyPrefix` of `"ibc"` should be used.
+- The `clientIdentifier` field identifies the client associated with this connection.
+- The `counterpartyClientIdentifier` field identifies the client on the counterparty chain associated with this connection.
+- The `version` field is an opaque string which can be utilised to determine encodings or protocols for channels or packets utilising this connection.
+ If not specified, a default `version` of `""` should be used.
+- The `delayPeriodTime` indicates a period in time that must elapse after validation of a header before a packet, acknowledgement, proof of receipt, or timeout can be processed.
+- The `delayPeriodBlocks` indicates a period in blocks that must elapse after validation of a header before a packet, acknowledgement, proof of receipt, or timeout can be processed.
+
+### Store paths
+
+Connection paths are stored under a unique identifier.
+
+```typescript
+function connectionPath(id: Identifier): Path {
+ return "connections/{id}"
+}
+```
+
+A reverse mapping from clients to a set of connections (utilised to look up all connections using a client) is stored under a unique prefix per-client:
+
+```typescript
+function clientConnectionsPath(clientIdentifier: Identifier): Path {
+ return "clients/{clientIdentifier}/connections"
+}
+```
+
+### Helper functions
+
+`addConnectionToClient` is used to add a connection identifier to the set of connections associated with a client.
+
+```typescript
+function addConnectionToClient(
+ clientIdentifier: Identifier,
+ connectionIdentifier: Identifier) {
+ conns = privateStore.get(clientConnectionsPath(clientIdentifier))
+ conns.add(connectionIdentifier)
+ privateStore.set(clientConnectionsPath(clientIdentifier), conns)
+}
+```
+
+Helper functions are defined by the connection to pass the `CommitmentPrefix` associated with the connection to the verification function
+provided by the client. In the other parts of the specifications, these functions MUST be used for introspecting other chains' state,
+instead of directly calling the verification functions on the client.
+
+```typescript
+function verifyClientConsensusState(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ clientIdentifier: Identifier,
+ consensusStateHeight: Height,
+ consensusState: ConsensusState
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, consensusStatePath(clientIdentifier, consensusStateHeight))
+ return verifyMembership(clientState, height, 0, 0, proof, path, consensusState)
+}
+
+function verifyClientState(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ clientState: ClientState
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, clientStatePath(clientIdentifier)
+ return verifyMembership(clientState, height, 0, 0, proof, path, clientState)
+}
+
+function verifyConnectionState(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ connectionIdentifier: Identifier,
+ connectionEnd: ConnectionEnd
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, connectionPath(connectionIdentifier))
+ return verifyMembership(clientState, height, 0, 0, proof, path, connectionEnd)
+}
+
+function verifyChannelState(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ channelEnd: ChannelEnd
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, channelPath(portIdentifier, channelIdentifier))
+ return verifyMembership(clientState, height, 0, 0, proof, path, channelEnd)
+}
+
+function verifyPacketCommitment(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ commitmentBytes: bytes
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, packetCommitmentPath(portIdentifier, channelIdentifier, sequence))
+ return verifyMembership(clientState, height, connection.delayPeriodTime, connection.delayPeriodBlocks, proof, path, commitmentBytes)
+}
+
+function verifyPacketAcknowledgement(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ acknowledgement: bytes
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, packetAcknowledgementPath(portIdentifier, channelIdentifier, sequence))
+ return verifyMembership(clientState, height, connection.delayPeriodTime, connection.delayPeriodBlocks, proof, path, acknowledgement)
+}
+
+function verifyPacketReceiptAbsence(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, packetReceiptPath(portIdentifier, channelIdentifier, sequence))
+ return verifyNonMembership(clientState, height, connection.delayPeriodTime, connection.delayPeriodBlocks, proof, path)
+}
+
+// OPTIONAL: verifyPacketReceipt is only required to support new channel types beyond ORDERED and UNORDERED.
+function verifyPacketReceipt(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ receipt: bytes
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, packetReceiptPath(portIdentifier, channelIdentifier, sequence))
+ return verifyMembership(clientState, height, connection.delayPeriodTime, connection.delayPeriodBlocks, connection.counterpartyPrefix, proof, portIdentifier, channelIdentifier, sequence, receipt)
+}
+
+function verifyNextSequenceRecv(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ sequence: uint64,
+ nextSequenceRecv: uint64
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(connection.counterpartyPrefix, nextSequenceRecvPath(portIdentifier, channelIdentifier, sequence))
+ return verifyMembership(clientState, height, connection.delayPeriodTime, connection.delayPeriodBlocks, proof, path, nextSequenceRecv)
+}
+
+function verifyMultihopMembership(
+ connection: ConnectionEnd, // the connection end corresponding to the receiving chain.
+ height: Height,
+ proof: MultihopProof,
+ connectionHops: []Identifier,
+ key: CommitmentPath,
+ value: bytes
+) {
+ // the connectionEnd corresponding to the end of the multi-hop channel path (sending/counterparty chain).
+ multihopConnectionEnd = abortTransactionUnless(getMultihopConnectionEnd(proof))
+ prefix = multihopConnectionEnd.GetCounterparty().GetPrefix()
+ client = queryClient(connection.clientIdentifier)
+ consensusState = queryConsensusState(connection.clientIdentifier, height)
+
+ abortTransactionUnless(client.Status() === "active")
+ abortTransactionUnless(client.GetLatestHeight() >= height)
+
+ // verify maximum delay period has passed
+ expectedTimePerBlock = queryMaxExpectedTimePerBlock()
+ delayPeriodTime = abortTransactionUnless(getMaximumDelayPeriod(proof, connection))
+ delayPeriodBlocks = getBlockDelay(delayPeriodTime, expectedTimePerBlock)
+ abortTransactionUnless(tendermint.VerifyDelayPeriodPassed(height, delayPeriodTime, delayPeriodBlocks))
+
+ return multihop.VerifyMultihopMembership(consensusState, connectionHops, proof, prefix, key, value) // see ics-033
+}
+
+function verifyMultihopNonMembership(
+ connection: ConnectionEnd, // the connection end corresponding to the receiving chain.
+ height: Height,
+ proof: MultihopProof,
+ connectionHops: Identifier[],
+ key: CommitmentPath
+) {
+ // the connectionEnd corresponding to the end of the multi-hop channel path (sending/counterparty chain).
+ multihopConnectionEnd = abortTransactionUnless(getMultihopConnectionEnd(proof))
+ prefix = multihopConnectionEnd.GetCounterparty().GetPrefix()
+ client = queryClient(connection.clientIdentifier)
+ consensusState = queryConsensusState(connection.clientIdentifier, height)
+
+ abortTransactionUnless(client.Status() === "active")
+ abortTransactionUnless(client.GetLatestHeight() >= height)
+
+ // verify maximum delay period has passed
+ expectedTimePerBlock = queryMaxExpectedTimePerBlock()
+ delayPeriodTime = abortTransactionUnless(getMaximumDelayPeriod(proof, connection))
+ delayPeriodBlocks = getBlockDelay(delayPeriodTime, expectedTimePerBlock)
+ abortTransactionUnless(tendermint.VerifyDelayPeriodPassed(height, delayPeriodTime, delayPeriodBlocks))
+
+ return multihop.VerifyMultihopNonMembership(consensusState, connectionHops, proof, prefix, key) // see ics-033
+}
+
+// Return the maximum expected time per block from the paramstore.
+// See 03-connection - GetMaxExpectedTimePerBlock.
+function queryMaxExpectedTimePerBlock(): uint64
+
+function getTimestampAtHeight(
+ connection: ConnectionEnd,
+ height: Height
+) {
+ return queryConsensusState(connection.clientIdentifier, height).getTimestamp()
+}
+
+// Return the connectionEnd corresponding to the source chain.
+function getMultihopConnectionEnd(proof: MultihopProof): ConnectionEnd {
+ return abortTransactionUnless(Unmarshal(proof.ConnectionProofs[proof.ConnectionProofs.length - 1].Value))
+}
+
+// Return the maximum delay period in seconds across all connections in the channel path.
+function getMaximumDelayPeriod(proof: MultihopProof, lastConnection: ConnectionEnd): number {
+ delayPeriodTime = lastConnection.GetDelayPeriod()
+ for connData in range proofs.ConnectionProofs {
+ connectionEnd = abortTransactionUnless(Unmarshal(connData.Value))
+ if (connectionEnd.DelayPeriod > delayPeriodTime) {
+ delayPeriodTime = connectionEnd.DelayPeriod
+ }
+ }
+ return delayPeriodTime
+}
+```
+
+### Sub-protocols
+
+This ICS defines the opening handshake subprotocol. Once opened, connections cannot be closed and identifiers cannot be reallocated (this prevents packet replay or authorisation confusion).
+
+Header tracking and misbehaviour detection are defined in [ICS 2](../ics-002-client-semantics).
+
+
+
+#### Identifier validation
+
+Connections are stored under a unique `Identifier` prefix.
+The validation function `validateConnectionIdentifier` MAY be provided.
+
+```typescript
+type validateConnectionIdentifier = (id: Identifier) => boolean
+```
+
+If not provided, the default `validateConnectionIdentifier` function will always return `true`.
+
+#### Versioning
+
+During the handshake process, two ends of a connection come to agreement on a
+version associated with that connection. This `Version` datatype is defined as:
+
+```typescript
+interface Version {
+ identifier: string
+ features: [string]
+}
+```
+
+The `identifier` field specifies a unique version identifier. A value of `"1"`
+specifies IBC 1.0.0.
+
+The `features` field specifies a list of features compatible with the specified
+identifier. The values `"ORDER_UNORDERED"` and `"ORDER_ORDERED"` specify
+unordered and ordered channels, respectively.
+
+Host state machine MUST utilise the version data to negotiate encodings,
+priorities, or connection-specific metadata related to custom logic on top of
+IBC. It is assumed that the two chains running the opening handshake have at
+least one compatible version in common (i.e., the compatible versions of the two
+chains must have a non-empty intersection). If the two chains do not have any
+mutually acceptable versions, the handshake will fail.
+
+An implementation MUST define a function `getCompatibleVersions` which returns the list of versions it supports, ranked by descending preference order.
+
+```typescript
+type getCompatibleVersions = () => [Version]
+```
+
+An implementation MUST define a function `pickVersion` to choose a version from a list of versions.
+
+```typescript
+type pickVersion = ([Version]) => Version
+```
+
+#### Opening Handshake
+
+The opening handshake sub-protocol serves to initialise consensus states for two chains on each other.
+
+The opening handshake defines four datagrams: *ConnOpenInit*, *ConnOpenTry*, *ConnOpenAck*, and *ConnOpenConfirm*.
+
+A correct protocol execution flows as follows (note that all calls are made through modules per ICS 25):
+
+| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
+| --------- | ----------------- | ---------------- | ------------------ | ---------------------- |
+| Actor | `ConnOpenInit` | A | (none, none) | (INIT, none) |
+| Relayer | `ConnOpenTry` | B | (INIT, none) | (INIT, TRYOPEN) |
+| Relayer | `ConnOpenAck` | A | (INIT, TRYOPEN) | (OPEN, TRYOPEN) |
+| Relayer | `ConnOpenConfirm` | B | (OPEN, TRYOPEN) | (OPEN, OPEN) |
+
+At the end of an opening handshake between two chains implementing the sub-protocol, the following properties hold:
+
+- Each chain has each other's correct consensus state as originally specified by the initiating actor.
+- Each chain has knowledge of and has agreed to its identifier on the other chain.
+
+This sub-protocol need not be permissioned, modulo anti-spam measures.
+
+Chains MUST implement a function `generateIdentifier` which chooses an identifier, e.g. by incrementing a counter:
+
+```typescript
+type generateIdentifier = () -> Identifier
+```
+
+A specific version can optionally be passed as `version` to ensure that the handshake will either complete with that version or fail.
+
+*ConnOpenInit* initialises a connection attempt on chain A.
+
+```typescript
+function connOpenInit(
+ counterpartyPrefix: CommitmentPrefix,
+ clientIdentifier: Identifier,
+ counterpartyClientIdentifier: Identifier,
+ version: string,
+ delayPeriodTime: uint64,
+ delayPeriodBlocks: uint64) {
+ // generate a new identifier
+ identifier = generateIdentifier()
+
+ abortTransactionUnless(queryClientState(clientIdentifier) !== null)
+ abortTransactionUnless(provableStore.get(connectionPath(identifier)) == null)
+
+ state = INIT
+ if version != "" {
+ // manually selected version must be one we can support
+ abortTransactionUnless(getCompatibleVersions().indexOf(version) > -1)
+ versions = [version]
+ } else {
+ versions = getCompatibleVersions()
+ }
+ connection = ConnectionEnd{state, "", counterpartyPrefix,
+ clientIdentifier, counterpartyClientIdentifier, versions, delayPeriodTime, delayPeriodBlocks}
+ provableStore.set(connectionPath(identifier), connection)
+ addConnectionToClient(clientIdentifier, identifier)
+}
+```
+
+*ConnOpenTry* relays notice of a connection attempt on chain A to chain B (this code is executed on chain B).
+
+```typescript
+function connOpenTry(
+ counterpartyConnectionIdentifier: Identifier,
+ counterpartyPrefix: CommitmentPrefix,
+ counterpartyClientIdentifier: Identifier,
+ clientIdentifier: Identifier,
+ clientState: ClientState, // DEPRECATED
+ counterpartyVersions: string[],
+ delayPeriodTime: uint64,
+ delayPeriodBlocks: uint64,
+ proofInit: CommitmentProof,
+ proofClient: CommitmentProof, // DEPRECATED
+ proofConsensus: CommitmentProof, // DEPRECATED
+ proofHeight: Height,
+ consensusHeight: Height,
+ hostConsensusStateProof?: bytes, // DEPRECATED
+) {
+ // generate a new identifier
+ identifier = generateIdentifier()
+
+ abortTransactionUnless(queryClientState(clientIdentifier) !== null)
+ expectedConnectionEnd = ConnectionEnd{INIT, "", getCommitmentPrefix(), counterpartyClientIdentifier,
+ clientIdentifier, counterpartyVersions, delayPeriodTime, delayPeriodBlocks}
+
+ versionsIntersection = intersection(counterpartyVersions, getCompatibleVersions())
+ version = pickVersion(versionsIntersection) // aborts transaction if there is no intersection
+
+ connection = ConnectionEnd{TRYOPEN, counterpartyConnectionIdentifier, counterpartyPrefix,
+ clientIdentifier, counterpartyClientIdentifier, [version], delayPeriodTime, delayPeriodBlocks}
+ abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofInit, counterpartyConnectionIdentifier, expectedConnectionEnd))
+
+ provableStore.set(connectionPath(identifier), connection)
+ addConnectionToClient(clientIdentifier, identifier)
+}
+```
+
+*ConnOpenAck* relays acceptance of a connection open attempt from chain B back to chain A (this code is executed on chain A).
+
+```typescript
+function connOpenAck(
+ identifier: Identifier,
+ clientState: ClientState, // DEPRECATED
+ version: string,
+ counterpartyIdentifier: Identifier,
+ proofTry: CommitmentProof,
+ proofClient: CommitmentProof, // DEPRECATED
+ proofConsensus: CommitmentProof, // DEPRECATED
+ proofHeight: Height,
+ consensusHeight: Height,
+ hostConsensusStateProof?: bytes, // DEPRECATED
+) {
+ connection = provableStore.get(connectionPath(identifier))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === INIT && connection.versions.indexOf(version) !== -1)
+ expectedConnectionEnd = ConnectionEnd{
+ TRYOPEN,
+ identifier,
+ getCommitmentPrefix(),
+ connection.counterpartyClientIdentifier,
+ connection.clientIdentifier,
+ [version],
+ connection.delayPeriodTime,
+ connection.delayPeriodBlocks
+ }
+ abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofTry, counterpartyIdentifier, expectedConnectionEnd))
+ connection.state = OPEN
+ connection.versions = [version]
+ connection.counterpartyConnectionIdentifier = counterpartyIdentifier
+ provableStore.set(connectionPath(identifier), connection)
+}
+```
+
+*ConnOpenConfirm* confirms opening of a connection on chain A to chain B, after which the connection is open on both chains (this code is executed on chain B).
+
+```typescript
+function connOpenConfirm(
+ identifier: Identifier,
+ proofAck: CommitmentProof,
+ proofHeight: Height) {
+ connection = provableStore.get(connectionPath(identifier))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === TRYOPEN)
+ expected = ConnectionEnd{OPEN, identifier, getCommitmentPrefix(), connection.counterpartyClientIdentifier,
+ connection.clientIdentifier, connection.versions, connection.delayPeriodTime, connection.delayPeriodBlocks}
+ abortTransactionUnless(connection.verifyConnectionState(proofHeight, proofAck, connection.counterpartyConnectionIdentifier, expected))
+ connection.state = OPEN
+ provableStore.set(connectionPath(identifier), connection)
+}
+```
+
+#### Querying
+
+Connections can be queried by identifier with `queryConnection`.
+
+```typescript
+function queryConnection(id: Identifier): ConnectionEnd | void {
+ return provableStore.get(connectionPath(id))
+}
+```
+
+Connections associated with a particular client can be queried by client identifier with `queryClientConnections`.
+
+```typescript
+function queryClientConnections(id: Identifier): Set {
+ return privateStore.get(clientConnectionsPath(id))
+}
+```
+
+### Properties & Invariants
+
+- Connection identifiers are first-come-first-serve: once a connection has been negotiated, a unique identifier pair exists between two chains.
+- The connection handshake cannot be man-in-the-middled by another blockchain's IBC handler.
+
+## Backwards Compatibility
+
+In the latest specification of the connection handshake, `connOpenTry` and `connOpenAck` will no longer validate that the counterparty's clien state and consensus state is a valid client of the executing chain's consensus protocol. Thus, `clientState`, `proofClient`, `proofConsensus` and `consensusHeight` fields in the `ConnOpenTry` and `ConnOpenACk` datagrams are deprecated and will eventually be removed.
+
+## Forwards Compatibility
+
+A future version of this ICS will include version negotiation in the opening handshake. Once a connection has been established and a version negotiated, future version updates can be negotiated per ICS 6.
+
+The consensus state can only be updated as allowed by the `updateConsensusState` function defined by the consensus protocol chosen when the connection is established.
+
+## Example Implementations
+
+- Implementation of ICS 03 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 03 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Parts of this document were inspired by the [previous IBC specification](https://github.com/cosmos/ibc/tree/main/archive).
+
+Mar 29, 2019 - Initial draft version submitted
+
+May 17, 2019 - Draft finalised
+
+Jul 29, 2019 - Revisions to track connection set associated with client
+
+Jul 27, 2022 - Addition of `ClientState` validation in `connOpenTry` and `connOpenAck`
+
+Jul 23, 2024 - [Removal of `ClientState` and `ConsensusState` validation in `connOpenTry` and `connOpenAck`](https://github.com/cosmos/ibc/pull/1128). For information on the consequences of these changes see the attached [diagram](./client-validation-removal.png) and [consequences document](./client-validation-removal)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.excalidraw b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.excalidraw
new file mode 100644
index 00000000..a3e04f22
--- /dev/null
+++ b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.excalidraw
@@ -0,0 +1,779 @@
+{
+ "type": "excalidraw",
+ "version": 2,
+ "source": "https://excalidraw.com",
+ "elements": [
+ {
+ "type": "rectangle",
+ "version": 352,
+ "versionNonce": 743366944,
+ "index": "a0",
+ "isDeleted": false,
+ "id": "Yd8PZmyx4ecAoEJeTN0gU",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 360.1384787370346,
+ "y": 233.86328125,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 393.76953125,
+ "height": 222.0703125,
+ "seed": 523580704,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "uQ3uhCc3hBNYBzhXURfgS",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721739352893,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 11,
+ "versionNonce": 973432032,
+ "index": "a1",
+ "isDeleted": false,
+ "id": "KqdrLNodfeliUlAaYd7U_",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 413,
+ "y": 213,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 72.93995666503906,
+ "height": 25,
+ "seed": 1588722912,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721644188803,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Chain A",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Chain A",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 590,
+ "versionNonce": 752964246,
+ "index": "a2",
+ "isDeleted": false,
+ "id": "39YVRup8859-A1EafFRqL",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1084.42578125,
+ "y": 236.17578125,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 351.1796875,
+ "height": 219.17022717039075,
+ "seed": 486520096,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "QN3CYjHvpZvUKeBl7mcVy",
+ "type": "arrow"
+ },
+ {
+ "id": "M9Ev5BXiStMWc7AiEqYoG",
+ "type": "arrow"
+ },
+ {
+ "id": "uQ3uhCc3hBNYBzhXURfgS",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721750252410,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 215,
+ "versionNonce": 58443040,
+ "index": "a3",
+ "isDeleted": false,
+ "id": "ZjCZc39bqWi0_Rg5gNQlI",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1156.171875,
+ "y": 212.65625,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 74.35995483398438,
+ "height": 25,
+ "seed": 1671870688,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721651877452,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Chain B",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Chain B",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 325,
+ "versionNonce": 1536376096,
+ "index": "a4",
+ "isDeleted": false,
+ "id": "uCslPrAM4Djw5ExOq0aUN",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 358.88671875,
+ "y": 636.76953125,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 395.90624999999994,
+ "height": 212.75781250000006,
+ "seed": 1646592288,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "QN3CYjHvpZvUKeBl7mcVy",
+ "type": "arrow"
+ },
+ {
+ "id": "M9Ev5BXiStMWc7AiEqYoG",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721651868077,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 107,
+ "versionNonce": 2051606816,
+ "index": "a6",
+ "isDeleted": false,
+ "id": "agwtoJMxeGS7bIuxaD4Bx",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 408.171875,
+ "y": 612.390625,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 72.699951171875,
+ "height": 25,
+ "seed": 1462127840,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721651871954,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Chain C",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Chain C",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 190,
+ "versionNonce": 1469958218,
+ "index": "a7",
+ "isDeleted": false,
+ "id": "CG88NZPlfuRalVY8yMxWp",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 385.79709345596996,
+ "y": 243.3760499715164,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 280.9679260253906,
+ "height": 126.07659386602299,
+ "seed": 784427296,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721750238830,
+ "link": null,
+ "locked": false,
+ "fontSize": 20.17225501856368,
+ "fontFamily": 1,
+ "text": "client-2 -> B\nconn-1 -> {self: client-2,\ncp: client-1, cp_conn: conn-3}\nchan-5 -> {connHops: conn-1, \ncp: chan-4}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client-2 -> B\nconn-1 -> {self: client-2,\ncp: client-1, cp_conn: conn-3}\nchan-5 -> {connHops: conn-1, \ncp: chan-4}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 223,
+ "versionNonce": 920780310,
+ "index": "a8",
+ "isDeleted": false,
+ "id": "gR93Hy-MkvblVRqLrG8kZ",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 396.2632724935363,
+ "y": 650.4820224935364,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 348.0797119140625,
+ "height": 125,
+ "seed": 1898438880,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721750263921,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client-2 -> B\nconn-1 -> {self: client-2, cp: client-1,\ncp_conn: conn-3}\nchan-5 -> {connHops: conn-1,\ncp: chan-4}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client-2 -> B\nconn-1 -> {self: client-2, cp: client-1,\ncp_conn: conn-3}\nchan-5 -> {connHops: conn-1,\ncp: chan-4}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 366,
+ "versionNonce": 902610326,
+ "index": "a9",
+ "isDeleted": false,
+ "id": "QQliPNdJCx7vEze58Q8JR",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1137.5972112021814,
+ "y": 248.57400344845155,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 277.23980712890625,
+ "height": 150,
+ "seed": 1979716896,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721750372396,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client-1 -> C\nconn-3 -> {self: client-1,\ncp: client-2, cp_conn:\nconn-1}\nchan-4 -> {connHops: conn-3,\ncp: chan-4}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client-1 -> C\nconn-3 -> {self: client-1,\ncp: client-2, cp_conn:\nconn-1}\nchan-4 -> {connHops: conn-3,\ncp: chan-4}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "rectangle",
+ "version": 137,
+ "versionNonce": 2111260554,
+ "index": "aA",
+ "isDeleted": false,
+ "id": "TKzr0vHyE4XWHulEuVtAT",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1090.48046875,
+ "y": 629.6252763560002,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 346.33508017548763,
+ "height": 202.90625,
+ "seed": 1665508576,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 3
+ },
+ "boundElements": [
+ {
+ "id": "H3lGRledKiu-WVXy3772m",
+ "type": "arrow"
+ }
+ ],
+ "updated": 1721750276225,
+ "link": null,
+ "locked": false
+ },
+ {
+ "type": "text",
+ "version": 11,
+ "versionNonce": 344204576,
+ "index": "aB",
+ "isDeleted": false,
+ "id": "3ZghdMy-mO97wkrk3EEBD",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1170,
+ "y": 606,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 75.41995239257812,
+ "height": 25,
+ "seed": 603720992,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721651737357,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "Chain D",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Chain D",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 243,
+ "versionNonce": 1221643850,
+ "index": "aC",
+ "isDeleted": false,
+ "id": "xvF8I7c4kFre8QJyPD3-A",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1122.1427689597338,
+ "y": 657.3267882330041,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 278.019775390625,
+ "height": 125,
+ "seed": 1058178272,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721750366371,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "client-1 -> C\nconn-3 -> {self: client-1,\ncp: client-2, cp_conn: conn-1}\nchan-4 -> {connHops: conn-3,\ncp: chan-4}",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "client-1 -> C\nconn-3 -> {self: client-1,\ncp: client-2, cp_conn: conn-1}\nchan-4 -> {connHops: conn-3,\ncp: chan-4}",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "arrow",
+ "version": 171,
+ "versionNonce": 1577314838,
+ "index": "aD",
+ "isDeleted": false,
+ "id": "uQ3uhCc3hBNYBzhXURfgS",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 767.0798849870346,
+ "y": 339.53714483330486,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 304.7833962629654,
+ "height": 0.38949285548875423,
+ "seed": 1929654496,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721750252410,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "Yd8PZmyx4ecAoEJeTN0gU",
+ "gap": 13.171875,
+ "focus": -0.0449448346714202
+ },
+ "endBinding": {
+ "elementId": "39YVRup8859-A1EafFRqL",
+ "gap": 12.5625,
+ "focus": 0.062414350761616284
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 304.7833962629654,
+ -0.38949285548875423
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 196,
+ "versionNonce": 823272726,
+ "index": "aF",
+ "isDeleted": false,
+ "id": "QN3CYjHvpZvUKeBl7mcVy",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 770.58984375,
+ "y": 752.9265113664123,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 304.46484375,
+ "height": 395.3092724618856,
+ "seed": 1974412512,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721750252410,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "uCslPrAM4Djw5ExOq0aUN",
+ "gap": 15.796875,
+ "focus": 0.790638045498287
+ },
+ "endBinding": {
+ "elementId": "39YVRup8859-A1EafFRqL",
+ "gap": 9.37109375,
+ "focus": 0.6762880872333856
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ 304.46484375,
+ -395.3092724618856
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 363,
+ "versionNonce": 1880119190,
+ "index": "aG",
+ "isDeleted": false,
+ "id": "M9Ev5BXiStMWc7AiEqYoG",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1068.80078125,
+ "y": 396.64863537568556,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 283.890625,
+ "height": 369.55917136427354,
+ "seed": 748094752,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721750252410,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "39YVRup8859-A1EafFRqL",
+ "gap": 15.625,
+ "focus": 0.5855959439003082
+ },
+ "endBinding": {
+ "elementId": "uCslPrAM4Djw5ExOq0aUN",
+ "gap": 30.1171875,
+ "focus": 0.8788301668262039
+ },
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -283.890625,
+ 369.55917136427354
+ ]
+ ]
+ },
+ {
+ "type": "arrow",
+ "version": 206,
+ "versionNonce": 1331283530,
+ "index": "aI",
+ "isDeleted": false,
+ "id": "H3lGRledKiu-WVXy3772m",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1072.14453125,
+ "y": 739.1045613784454,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 243.84375,
+ "height": 10.661063621554604,
+ "seed": 1858638048,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": {
+ "type": 2
+ },
+ "boundElements": [],
+ "updated": 1721750276225,
+ "link": null,
+ "locked": false,
+ "startBinding": {
+ "elementId": "TKzr0vHyE4XWHulEuVtAT",
+ "gap": 18.3359375,
+ "focus": 0.0031785684933362287
+ },
+ "endBinding": null,
+ "lastCommittedPoint": null,
+ "startArrowhead": null,
+ "endArrowhead": "arrow",
+ "points": [
+ [
+ 0,
+ 0
+ ],
+ [
+ -243.84375,
+ 10.661063621554604
+ ]
+ ]
+ },
+ {
+ "type": "text",
+ "version": 1096,
+ "versionNonce": 1015133408,
+ "index": "aJ",
+ "isDeleted": false,
+ "id": "Ug5AA066r4v83c6qyYzA6",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 430.29296875,
+ "y": 895.41015625,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 800.4794311523438,
+ "height": 400,
+ "seed": 127919392,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721663260385,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "SendPacket from Chain A: Not receivable by Chain B\n\nSendPacket from Chain B: Receivable by Chain C\nReceivable by Chain A. This token is double spent into a misconfigured chain\nSending from A back to B will fail. Thus it is only redeemable by Chain C\n\nSendPacket from Chain C: Receivable by Chain B\nReceivable by Chain D. This token is double spent into a misconfigured chain.\nSending from D back to C will fail. Thus it is redeemable only by Chain B\n\nOther packet flow messages passing will require coincidences.\nSuppose Chain A and Chain C sends the exact same SendPacket\nThe chain C packet will be receivable by Chain B which writes an ACK\nBoth chain C and chain A will acknowledge the packet as if it is meant for them\nbut only chain C acknowledgement logic is valid\n",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "SendPacket from Chain A: Not receivable by Chain B\n\nSendPacket from Chain B: Receivable by Chain C\nReceivable by Chain A. This token is double spent into a misconfigured chain\nSending from A back to B will fail. Thus it is only redeemable by Chain C\n\nSendPacket from Chain C: Receivable by Chain B\nReceivable by Chain D. This token is double spent into a misconfigured chain.\nSending from D back to C will fail. Thus it is redeemable only by Chain B\n\nOther packet flow messages passing will require coincidences.\nSuppose Chain A and Chain C sends the exact same SendPacket\nThe chain C packet will be receivable by Chain B which writes an ACK\nBoth chain C and chain A will acknowledge the packet as if it is meant for them\nbut only chain C acknowledgement logic is valid\n",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "type": "text",
+ "version": 1052,
+ "versionNonce": 1370680544,
+ "index": "aK",
+ "isDeleted": false,
+ "id": "JAht8WVr_9XdR-kX-m8XY",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "angle": 0,
+ "x": 1776.2769145021512,
+ "y": 253.02825896369697,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "width": 688.41943359375,
+ "height": 450,
+ "seed": 1676632288,
+ "groupIds": [],
+ "frameId": null,
+ "roundness": null,
+ "boundElements": [],
+ "updated": 1721749920023,
+ "link": null,
+ "locked": false,
+ "fontSize": 20,
+ "fontFamily": 1,
+ "text": "What does this require?\nCoincidence of identifier for clients. Chain A and Chain C\nuse same client identifier for chain B\nChain D and chain B use the same client identifier for chain C\nCoincidence of identifiers in connection identifiers\nCoincidence of timing in the Connection Handshake. e..g. B-C connection\nhandshake cannot be complete before chain A starts handshake\nCoincidence of identifiers in the channel identifiers\nCoincidence of timing in the Channel Handshake\n\nFull packet flow is only possible on valid connection/channel\nPartial flows possible only on misconfigured chains\nFull packet flow only possible on misconfigured chains if there\nis simultaneously a coincidence of valid packet flow\n\nChain B and Chain C are never confused about who they are\ntalking to.\nTheir respective escrow accounts are safe.",
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "What does this require?\nCoincidence of identifier for clients. Chain A and Chain C\nuse same client identifier for chain B\nChain D and chain B use the same client identifier for chain C\nCoincidence of identifiers in connection identifiers\nCoincidence of timing in the Connection Handshake. e..g. B-C connection\nhandshake cannot be complete before chain A starts handshake\nCoincidence of identifiers in the channel identifiers\nCoincidence of timing in the Channel Handshake\n\nFull packet flow is only possible on valid connection/channel\nPartial flows possible only on misconfigured chains\nFull packet flow only possible on misconfigured chains if there\nis simultaneously a coincidence of valid packet flow\n\nChain B and Chain C are never confused about who they are\ntalking to.\nTheir respective escrow accounts are safe.",
+ "autoResize": true,
+ "lineHeight": 1.25
+ },
+ {
+ "id": "3IJ-NJxV8lB6HabKu1plS",
+ "type": "text",
+ "x": 417.7445902901098,
+ "y": 105.40244979747675,
+ "width": 749.9193115234375,
+ "height": 25,
+ "angle": 0,
+ "strokeColor": "#1e1e1e",
+ "backgroundColor": "transparent",
+ "fillStyle": "solid",
+ "strokeWidth": 2,
+ "strokeStyle": "solid",
+ "roughness": 1,
+ "opacity": 100,
+ "groupIds": [],
+ "frameId": null,
+ "index": "aL",
+ "roundness": null,
+ "seed": 1019693834,
+ "version": 182,
+ "versionNonce": 1387079766,
+ "isDeleted": false,
+ "boundElements": null,
+ "updated": 1721750596473,
+ "link": null,
+ "locked": false,
+ "text": "Potential consequences of removing client validation in connection handshake",
+ "fontSize": 20,
+ "fontFamily": 1,
+ "textAlign": "left",
+ "verticalAlign": "top",
+ "containerId": null,
+ "originalText": "Potential consequences of removing client validation in connection handshake",
+ "autoResize": true,
+ "lineHeight": 1.25
+ }
+ ],
+ "appState": {
+ "gridSize": null,
+ "viewBackgroundColor": "#ffffff"
+ },
+ "files": {}
+}
\ No newline at end of file
diff --git a/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.mdx b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.mdx
new file mode 100644
index 00000000..5ad6cfcb
--- /dev/null
+++ b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.mdx
@@ -0,0 +1,31 @@
+---
+title: "Client Validation Removal"
+---
+
+The IBC protocol is no longer requiring that chains verify that the underlying counterparty client of a connection is a valid client of their consensus protocol. In order to understand the motivation for these changes, this document will describe why the validation existed in the first place, what challenges they introduced, and what the consequences are of removing them.
+
+## Client Validation Motivation
+
+Client validation was initially included in the connection handshake handlers in order to ensure that both chains were talking to the correct counterparty, i.e. the connection was correctly configured such that both chains are talking to each other. This ensures that when a channel is built on top of that connection, and a packet is sent over that channel, only a single chain is capable of receiving the packet.
+
+IBC relies on locally unique identifiers for each chain's client, connection and channel identifiers. Thus, the identifiers, connection struct and channel struct are not unique to a chain. The only thing that uniquely identifies a chain is its consensus. Specifically, the tuple of `(height, chainID, validatorSet)` is unique for an honest chain. This tuple is committed to by the headers a validator set produces. Thus, if a chain A has a client with a consensus state that is committing to a tuple uniquely identifying chain B; then chain B can be sure that the client on chain A is a client of itself. Thus any connection on chain A built on top of that client is meant to connect to chain B.
+
+The IBC connection handshake used this fact to ensure that the connection handshake only completes for the chains that are directly pointing to one another. If a relayer misconfigured the connection handshake by choosing the wrong counterparty client identifer on `ConnOpenInit`, the handshake would fail on `ConnOpenAck` when the counterparty client is proven to not be a valid client of the initializing chain. Thus, misconfigured connection attempts are blocked from completing. When a connection moves to `OPEN`, we can be sure that only the two chains in question are connected to each other.
+
+Without this check, it is possible in very unlucky circumstances to have two chains that are validly connected to each other and also have misconfigured third-party chains that believe they are connected to a chain that is not connected back to them. For an example of this situation, see the attached [diagram](./client-validation-removal.png). In this case, the validly connected chains will have communication between them that follows IBC's correctness and integrity properties. However, the chains that are misconfigured may misinterpret messages sent to other chains as messages intended for them. In this case, the only connection ends that are affected are the misconfigured connection ends that do not correlate to a valid connection end on the intended counterparty. In order for this situation to arise, there would need to not only be relayer error, but also a coincidence in identifiers and handshake message timing. Thus, this is an unlikely situation to arise in practice and the only effect is invalid message processing on misconfigured connections and channel ends.
+
+## Client Validation Problems
+
+While it is beneficial that misconfigured connection attempts are blocked from completing, the client validation in the connection handshake introduced a lot of problems for the upgradability and flexibility of the protocol.
+
+- Not all chains have the ability to introspect their own consensus, specifically their own consensus history which is required to validate a counterparty's previous consensus state.
+- Explicit verification of a counterparty client state and consensus state makes adding new implementions of the same consensus difficult since the validation of any new client implementations must be supported on the counterparty you want to use it with. Thus, the structure of `ClientState` and `ConsensusState` is very difficult to change without interchain coordination.
+- Similarly, the proofs rely on ICS24 paths for the `ClientState` and `ConsensusState`. Thus, changing the key paths to a more efficient representation is very difficult without interchain coordination.
+
+## Social Consensus
+
+As mentioned above, the client validation in the connection handshake prevents the creation of `OPEN` misconfigured connections and channels. However, it does not prevent the creation of `OPEN` connections and channels that are opened to malicious chains. The way IBC handles these situations is to rely on social consensus. This can be in the form of explicit social consensus, i.e. governance approved clients, connections and channels; or implicit social consensus where IBC messages are permissionless but there exists an out-of-band consensus on which connection ID is the canonical connection that all users will use to communicate to an external chain. This out-of-band consensus can be reflected in chain registries and front ends that are reflected to end users to prevent them from unintentionally using the wrong channel.
+
+Thus, social consensus is already a key element of how IBC functions for end users. We can extend the use of this pre-existing social consensus to also prevent users from sending on misconfigured connection ends rather than enforcing validation in the protocol. By removing the validation, we vastly simplify the connection handshake protocol: removing two proof verifications in both `ConnOpenTry` and `ConnOpenAck`. Ledgers no longer need to track their own consensus in order to explicitly validate a counterparty's client. This gives much more freedom and flexibility to how clients are implemented and written into state.
+
+While removing client validation in the connection handshake does open the slight possibility of misconfigured but still usable channels, social consensus will be relied on going forward to ensure these misconfigured channels do not get usage.
diff --git a/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.png b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.png
new file mode 100644
index 00000000..f5a6d354
Binary files /dev/null and b/ibc/next/spec/core/ics-003-connection-semantics/client-validation-removal.png differ
diff --git a/ibc/next/spec/core/ics-003-connection-semantics/state.png b/ibc/next/spec/core/ics-003-connection-semantics/state.png
new file mode 100644
index 00000000..4b7cb83d
Binary files /dev/null and b/ibc/next/spec/core/ics-003-connection-semantics/state.png differ
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/README.mdx b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/README.mdx
new file mode 100644
index 00000000..26cc5fa4
--- /dev/null
+++ b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/README.mdx
@@ -0,0 +1,1547 @@
+---
+ics: 4
+title: Channel & Packet Semantics
+stage: draft
+category: IBC/TAO
+kind: instantiation
+requires: [2, 3, 5, 24]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-03-07
+modified: 2019-08-25
+---
+
+## Synopsis
+
+The "channel" abstraction provides message delivery semantics to the interblockchain communication protocol, in three categories: ordering, exactly-once delivery, and module permissioning. A channel serves as a conduit for packets passing between a module on one chain and a module on another, ensuring that packets are executed only once, delivered in the order in which they were sent (if necessary), and delivered only to the corresponding module owning the other end of the channel on the destination chain. Each channel is associated with a particular connection, and a connection may have any number of associated channels, allowing the use of common identifiers and amortising the cost of header verification across all the channels utilising a connection & light client.
+
+Channels are payload-agnostic. The modules which send and receive IBC packets decide how to construct packet data and how to act upon the incoming packet data, and must utilise their own application logic to determine which state transactions to apply according to what data the packet contains.
+
+### Motivation
+
+The interblockchain communication protocol uses a cross-chain message passing model. IBC *packets* are relayed from one blockchain to the other by external relayer processes. Chain `A` and chain `B` confirm new blocks independently, and packets from one chain to the other may be delayed, censored, or re-ordered arbitrarily. Packets are visible to relayers and can be read from a blockchain by any relayer process and submitted to any other blockchain.
+
+The IBC protocol must provide ordering (for ordered channels) and exactly-once delivery guarantees to allow applications to reason about the combined state of connected modules on two chains.
+
+> **Example**: An application may wish to allow a single tokenized asset to be transferred between and held on multiple blockchains while preserving fungibility and conservation of supply. The application can mint asset vouchers on chain `B` when a particular IBC packet is committed to chain `B`, and require outgoing sends of that packet on chain `A` to escrow an equal amount of the asset on chain `A` until the vouchers are later redeemed back to chain `A` with an IBC packet in the reverse direction. This ordering guarantee along with correct application logic can ensure that total supply is preserved across both chains and that any vouchers minted on chain `B` can later be redeemed back to chain `A`.
+
+In order to provide the desired ordering, exactly-once delivery, and module permissioning semantics to the application layer, the interblockchain communication protocol must implement an abstraction to enforce these semantics — channels are this abstraction.
+
+### Definitions
+
+`ConsensusState` is as defined in [ICS 2](../ics-002-client-semantics).
+
+`Connection` is as defined in [ICS 3](../ics-003-connection-semantics).
+
+`Port` and `authenticateCapability` are as defined in [ICS 5](../ics-005-port-allocation).
+
+`hash` is a generic collision-resistant hash function, the specifics of which must be agreed on by the modules utilising the channel. `hash` can be defined differently by different chains.
+
+`Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in [ICS 24](../ics-024-host-requirements).
+
+See [upgrades spec](./UPGRADES) for definition of `pendingInflightPackets` and `restoreChannel`.
+
+A *channel* is a pipeline for exactly-once packet delivery between specific modules on separate blockchains, which has at least one end capable of sending packets and one end capable of receiving packets.
+
+A *bidirectional* channel is a channel where packets can flow in both directions: from `A` to `B` and from `B` to `A`.
+
+A *unidirectional* channel is a channel where packets can only flow in one direction: from `A` to `B` (or from `B` to `A`, the order of naming is arbitrary).
+
+An *ordered* channel is a channel where packets are delivered exactly in the order which they were sent. This channel type offers a very strict guarantee of ordering. Either, the packets are received in the order they were sent, or if a packet in the sequence times out; then all future packets are also not receivable and the channel closes.
+
+An *ordered_allow_timeout* channel is a less strict version of the *ordered* channel. Here, the channel logic will take a *best effort* approach to delivering the packets in order. In a stream of packets, the channel will relay all packets in order and if a packet in the stream times out, the timeout logic for that packet will execute and the rest of the later packets will continue processing in order. Thus, we **do not close** the channel on a timeout with this channel type.
+
+An *unordered* channel is a channel where packets can be delivered in any order, which may differ from the order in which they were sent.
+
+```typescript
+enum ChannelOrder {
+ ORDERED,
+ UNORDERED,
+ ORDERED_ALLOW_TIMEOUT,
+}
+```
+
+Directionality and ordering are independent, so one can speak of a bidirectional unordered channel, a unidirectional ordered channel, etc.
+
+All channels provide exactly-once packet delivery, meaning that a packet sent on one end of a channel is delivered no more and no less than once, eventually, to the other end.
+
+This specification only concerns itself with *bidirectional* channels. *Unidirectional* channels can use almost exactly the same protocol and will be outlined in a future ICS.
+
+An end of a channel is a data structure on one chain storing channel metadata:
+
+```typescript
+interface ChannelEnd {
+ state: ChannelState
+ ordering: ChannelOrder
+ counterpartyPortIdentifier: Identifier
+ counterpartyChannelIdentifier: Identifier
+ connectionHops: [Identifier]
+ version: string
+ upgradeSequence: uint64
+}
+```
+
+- The `state` is the current state of the channel end.
+- The `ordering` field indicates whether the channel is `unordered`, `ordered`, or `ordered_allow_timeout`.
+- The `counterpartyPortIdentifier` identifies the port on the counterparty chain which owns the other end of the channel.
+- The `counterpartyChannelIdentifier` identifies the channel end on the counterparty chain.
+- The `nextSequenceSend`, stored separately, tracks the sequence number for the next packet to be sent.
+- The `nextSequenceRecv`, stored separately, tracks the sequence number for the next packet to be received.
+- The `nextSequenceAck`, stored separately, tracks the sequence number for the next packet to be acknowledged.
+- The `connectionHops` stores the list of connection identifiers ordered starting from the receiving end towards the sender. `connectionHops[0]` is the connection end on the receiving chain. More than one connection hop indicates a multi-hop channel.
+- The `version` string stores an opaque channel version, which is agreed upon during the handshake. This can determine module-level configuration such as which packet encoding is used for the channel. This version is not used by the core IBC protocol. If the version string contains structured metadata for the application to parse and interpret, then it is considered best practice to encode all metadata in a JSON struct and include the marshalled string in the version field.
+
+See the [upgrade spec](./UPGRADES) for details on `upgradeSequence`.
+
+Channel ends have a *state*:
+
+```typescript
+enum ChannelState {
+ INIT,
+ TRYOPEN,
+ OPEN,
+ CLOSED,
+ FLUSHING,
+ FLUSHINGCOMPLETE,
+}
+```
+
+- A channel end in `INIT` state has just started the opening handshake.
+- A channel end in `TRYOPEN` state has acknowledged the handshake step on the counterparty chain.
+- A channel end in `OPEN` state has completed the handshake and is ready to send and receive packets.
+- A channel end in `CLOSED` state has been closed and can no longer be used to send or receive packets.
+
+See the [upgrade spec](./UPGRADES) for details on `FLUSHING` and `FLUSHCOMPLETE`.
+
+A `Packet`, in the interblockchain communication protocol, is a particular interface defined as follows:
+
+```typescript
+interface Packet {
+ sequence: uint64
+ timeoutHeight: Height
+ timeoutTimestamp: uint64
+ sourcePort: Identifier
+ sourceChannel: Identifier
+ destPort: Identifier
+ destChannel: Identifier
+ data: bytes
+}
+```
+
+- The `sequence` number corresponds to the order of sends and receives, where a packet with an earlier sequence number must be sent and received before a packet with a later sequence number.
+- The `timeoutHeight` indicates a consensus height on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out.
+- The `timeoutTimestamp` indicates a timestamp on the destination chain after which the packet will no longer be processed, and will instead count as having timed-out.
+- The `sourcePort` identifies the port on the sending chain.
+- The `sourceChannel` identifies the channel end on the sending chain.
+- The `destPort` identifies the port on the receiving chain.
+- The `destChannel` identifies the channel end on the receiving chain.
+- The `data` is an opaque value which can be defined by the application logic of the associated modules.
+
+Note that a `Packet` is never directly serialised. Rather it is an intermediary structure used in certain function calls that may need to be created or processed by modules calling the IBC handler.
+
+An `OpaquePacket` is a packet, but cloaked in an obscuring data type by the host state machine, such that a module cannot act upon it other than to pass it to the IBC handler. The IBC handler can cast a `Packet` to an `OpaquePacket` and vice versa.
+
+```typescript
+type OpaquePacket = object
+```
+
+In order to enable new channel types (e.g. ORDERED_ALLOW_TIMEOUT), the protocol introduces standardized packet receipts that will serve as sentinel values for the receiving chain to explicitly write to its store the outcome of a `recvPacket`.
+
+```typescript
+enum PacketReceipt {
+ SUCCESSFUL_RECEIPT,
+ TIMEOUT_RECEIPT,
+}
+```
+
+### Desired Properties
+
+#### Efficiency
+
+- The speed of packet transmission and confirmation should be limited only by the speed of the underlying chains.
+ Proofs should be batchable where possible.
+
+#### Exactly-once delivery
+
+- IBC packets sent on one end of a channel should be delivered exactly once to the other end.
+- No network synchrony assumptions should be required for exactly-once safety.
+ If one or both of the chains halt, packets may be delivered no more than once, and once the chains resume packets should be able to flow again.
+
+#### Ordering
+
+- On *ordered* channels, packets should be sent and received in the same order: if packet *x* is sent before packet *y* by a channel end on chain `A`, packet *x* must be received before packet *y* by the corresponding channel end on chain `B`. If packet *x* is sent before packet *y* by a channel and packet *x* is timed out; then packet *y* and any packet sent after *x* cannot be received.
+- On *ordered_allow_timeout* channels, packets should be sent and received in the same order: if packet *x* is sent before packet *y* by a channel end on chain `A`, packet *x* must be received **or** timed out before packet *y* by the corresponding channel end on chain `B`.
+- On *unordered* channels, packets may be sent and received in any order. Unordered packets, like ordered packets, have individual timeouts specified in terms of the destination chain's height.
+
+#### Permissioning
+
+- Channels should be permissioned to one module on each end, determined during the handshake and immutable afterwards (higher-level logic could tokenize channel ownership by tokenising ownership of the port).
+ Only the module associated with a channel end should be able to send or receive on it.
+
+## Technical Specification
+
+### Dataflow visualisation
+
+The architecture of clients, connections, channels and packets:
+
+
+
+### Preliminaries
+
+#### Store paths
+
+Channel structures are stored under a store path prefix unique to a combination of a port identifier and channel identifier:
+
+```typescript
+function channelPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "channelEnds/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+```
+
+The capability key associated with a channel is stored under the `channelCapabilityPath`:
+
+```typescript
+function channelCapabilityPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "{channelPath(portIdentifier, channelIdentifier)}/key"
+}
+```
+
+The `nextSequenceSend`, `nextSequenceRecv`, and `nextSequenceAck` unsigned integer counters are stored separately so they can be proved individually:
+
+```typescript
+function nextSequenceSendPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "nextSequenceSend/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+
+function nextSequenceRecvPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "nextSequenceRecv/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+
+function nextSequenceAckPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "nextSequenceAck/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+```
+
+Constant-size commitments to packet data fields are stored under the packet sequence number:
+
+```typescript
+function packetCommitmentPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
+ return "commitments/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
+}
+```
+
+Absence of the path in the store is equivalent to a zero-bit.
+
+Packet receipt data are stored under the `packetReceiptPath`. In the case of a successful receive, the destination chain writes a sentinel success value of `SUCCESSFUL_RECEIPT`.
+Some channel types MAY write a sentinel timeout value `TIMEOUT_RECEIPT` if the packet is received after the specified timeout.
+
+```typescript
+function packetReceiptPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
+ return "receipts/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
+}
+```
+
+Packet acknowledgement data are stored under the `packetAcknowledgementPath`:
+
+```typescript
+function packetAcknowledgementPath(portIdentifier: Identifier, channelIdentifier: Identifier, sequence: uint64): Path {
+ return "acks/ports/{portIdentifier}/channels/{channelIdentifier}/sequences/{sequence}"
+}
+```
+
+### Versioning
+
+During the handshake process, two ends of a channel come to agreement on a version bytestring associated
+with that channel. The contents of this version bytestring are and will remain opaque to the IBC core protocol.
+Host state machines MAY utilise the version data to indicate supported IBC/APP protocols, agree on packet
+encoding formats, or negotiate other channel-related metadata related to custom logic on top of IBC.
+
+Host state machines MAY also safely ignore the version data or specify an empty string.
+
+### Sub-protocols
+
+> Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter.
+
+#### Identifier validation
+
+Channels are stored under a unique `(portIdentifier, channelIdentifier)` prefix.
+The validation function `validatePortIdentifier` MAY be provided.
+
+```typescript
+type validateChannelIdentifier = (portIdentifier: Identifier, channelIdentifier: Identifier) => boolean
+```
+
+If not provided, the default `validateChannelIdentifier` function will always return `true`.
+
+#### Channel lifecycle management
+
+
+
+| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
+| --------- | ---------------- | ---------------- | ------------------ | ---------------------- |
+| Actor | ChanOpenInit | A | (none, none) | (INIT, none) |
+| Relayer | ChanOpenTry | B | (INIT, none) | (INIT, TRYOPEN) |
+| Relayer | ChanOpenAck | A | (INIT, TRYOPEN) | (OPEN, TRYOPEN) |
+| Relayer | ChanOpenConfirm | B | (OPEN, TRYOPEN) | (OPEN, OPEN) |
+
+| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
+| --------- | ---------------- | ---------------- | ------------------ | ---------------------- |
+| Actor | ChanCloseInit | A | (OPEN, OPEN) | (CLOSED, OPEN) |
+| Relayer | ChanCloseConfirm | B | (CLOSED, OPEN) | (CLOSED, CLOSED) |
+| Actor | ChanCloseFrozen | A or B | (OPEN, OPEN) | (CLOSED, CLOSED) |
+
+##### Opening handshake
+
+The `chanOpenInit` function is called by a module to initiate a channel opening handshake with a module on another chain. Functions `chanOpenInit` and `chanOpenTry` do no set the new channel end in state because the channel version might be modified by the application callback. A function `writeChannel` should be used to write the channel end in state after executing the application callback:
+
+```typescript
+function writeChannel(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ state: ChannelState,
+ order: ChannelOrder,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ connectionHops: [Identifier],
+ version: string) {
+ channel = ChannelEnd{
+ state, order,
+ counterpartyPortIdentifier, counterpartyChannelIdentifier,
+ connectionHops, version
+ }
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+}
+```
+
+See handler functions `handleChanOpenInit` and `handleChanOpenTry` in [Channel lifecycle management](../ics-026-routing-module/README#channel-lifecycle-management) for more details.
+
+The opening channel must provide the identifiers of the local channel identifier, local port, remote port, and remote channel identifier.
+
+When the opening handshake is complete, the module which initiates the handshake will own the end of the created channel on the host ledger, and the counterparty module which
+it specifies will own the other end of the created channel on the counterparty chain. Once a channel is created, ownership cannot be changed (although higher-level abstractions
+could be implemented to provide this).
+
+Chains MUST implement a function `generateIdentifier` which chooses an identifier, e.g. by incrementing a counter:
+
+```typescript
+type generateIdentifier = () -> Identifier
+```
+
+```typescript
+function chanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier): (channelIdentifier: Identifier, channelCapability: CapabilityKey) {
+ channelIdentifier = generateIdentifier()
+ abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))
+
+ abortTransactionUnless(provableStore.get(channelPath(portIdentifier, channelIdentifier)) === null)
+ connection = provableStore.get(connectionPath(connectionHops[0]))
+
+ // optimistic channel handshakes are allowed
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
+
+ channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
+ provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
+ provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
+ provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1)
+
+ return channelIdentifier, channelCapability
+}
+```
+
+The `chanOpenTry` function is called by a module to accept the first step of a channel opening handshake initiated by a module on another chain.
+
+```typescript
+function chanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string,
+ proofInit: CommitmentProof | MultihopProof,
+ proofHeight: Height): (channelIdentifier: Identifier, channelCapability: CapabilityKey) {
+ channelIdentifier = generateIdentifier()
+
+ abortTransactionUnless(validateChannelIdentifier(portIdentifier, channelIdentifier))
+ abortTransactionUnless(authenticateCapability(portPath(portIdentifier), portCapability))
+
+ connection = provableStore.get(connectionPath(connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // return hops from counterparty's view
+ counterpartyHops = getCounterPartyHops(proofInit, connection)
+
+ expected = ChannelEnd{
+ INIT, order, portIdentifier,
+ "", counterpartyHops,
+ counterpartyVersion
+ }
+
+ if (connectionHops.length > 1) {
+ key = channelPath(counterparty.PortId, counterparty.ChannelId)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofInit,
+ connectionHops,
+ key
+ expected))
+ } else {
+ abortTransactionUnless(connection.verifyChannelState(
+ proofHeight,
+ proofInit,
+ counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ expected
+ ))
+ }
+
+ channelCapability = newCapability(channelCapabilityPath(portIdentifier, channelIdentifier))
+
+ // initialize channel sequences
+ provableStore.set(nextSequenceSendPath(portIdentifier, channelIdentifier), 1)
+ provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
+ provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1)
+
+ return channelIdentifier, channelCapability
+}
+```
+
+The `chanOpenAck` is called by the handshake-originating module to acknowledge the acceptance of the initial request by the
+counterparty module on the other chain.
+
+```typescript
+function chanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string,
+ proofTry: CommitmentProof | MultihopProof,
+ proofHeight: Height) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === INIT)
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // return hops from counterparty's view
+ counterpartyHops = getCounterPartyHops(proofTry, connection)
+
+ expected = ChannelEnd{TRYOPEN, channel.order, portIdentifier,
+ channelIdentifier, counterpartyHops, counterpartyVersion}
+
+ if (channel.connectionHops.length > 1) {
+ key = channelPath(counterparty.PortId, counterparty.ChannelId)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofTry,
+ channel.connectionHops,
+ key,
+ expected))
+ } else {
+ abortTransactionUnless(connection.verifyChannelState(
+ proofHeight,
+ proofTry,
+ channel.counterpartyPortIdentifier,
+ counterpartyChannelIdentifier,
+ expected
+ ))
+ }
+ // write will happen in the handler defined in the ICS26 spec
+}
+```
+
+The `chanOpenConfirm` function is called by the handshake-accepting module to acknowledge the acknowledgement
+of the handshake-originating module on the other chain and finish the channel opening handshake.
+
+```typescript
+function chanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proofAck: CommitmentProof | MultihopProof,
+ proofHeight: Height) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state === TRYOPEN)
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // return hops from counterparty's view
+ counterpartyHops = getCounterPartyHops(proofAck, connection)
+
+ expected = ChannelEnd{OPEN, channel.order, portIdentifier,
+ channelIdentifier, counterpartyHops, channel.version}
+
+ if (connectionHops.length > 1) {
+ key = channelPath(counterparty.PortId, counterparty.ChannelId)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofAck,
+ channel.connectionHops,
+ key
+ expected))
+ } else {
+ abortTransactionUnless(connection.verifyChannelState(
+ proofHeight,
+ proofAck,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ expected
+ ))
+ }
+
+ // write will happen in the handler defined in the ICS26 spec
+}
+```
+
+##### Closing handshake
+
+The `chanCloseInit` function is called by either module to close their end of the channel. Once closed, channels cannot be reopened.
+
+Calling modules MAY atomically execute appropriate application logic in conjunction with calling `chanCloseInit`.
+
+Any in-flight packets can be timed-out as soon as a channel is closed.
+
+```typescript
+function chanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state !== CLOSED)
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+ channel.state = CLOSED
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+}
+```
+
+The `chanCloseConfirm` function is called by the counterparty module to close their end of the channel,
+since the other end has been closed.
+
+Calling modules MAY atomically execute appropriate application logic in conjunction with calling `chanCloseConfirm`.
+
+Once closed, channels cannot be reopened and identifiers cannot be reused. Identifier reuse is prevented because
+we want to prevent potential replay of previously sent packets. The replay problem is analogous to using sequence
+numbers with signed messages, except where the light client algorithm "signs" the messages (IBC packets), and the replay
+prevention sequence is the combination of port identifier, channel identifier, and packet sequence - hence we cannot
+allow the same port identifier & channel identifier to be reused again with a sequence reset to zero, since this
+might allow packets to be replayed. It would be possible to safely reuse identifiers if timeouts of a particular
+maximum height/time were mandated & tracked, and future specification versions may incorporate this feature.
+
+```typescript
+function chanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proofInit: CommitmentProof | MultihopProof,
+ proofHeight: Height) {
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state !== CLOSED)
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // return hops from counterparty's view
+ counterpartyHops = getCounterPartyHops(proofInit, connection)
+
+ expected = ChannelEnd{CLOSED, channel.order, portIdentifier,
+ channelIdentifier, counterpartyHops, channel.version}
+
+ if (connectionHops.length > 1) {
+ key = channelPath(counterparty.PortId, counterparty.ChannelId)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofInit,
+ channel.connectionHops,
+ key
+ expected))
+ } else {
+ abortTransactionUnless(connection.verifyChannelState(
+ proofHeight,
+ proofInit,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ expected
+ ))
+ }
+
+ // write may happen asynchronously in the handler defined in the ICS26 spec
+ // if the channel is closing during an upgrade,
+ // then we can delete all auxiliary upgrade information
+ provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier))
+ privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier))
+
+ channel.state = CLOSED
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+}
+```
+
+The `chanCloseFrozen` function is called by a relayer to force close a multi-hop channel if any client state in the
+channel path is frozen. A relayer should send proof of the frozen client state to each end of the channel with a
+proof of the frozen client state in the channel path starting from each channel end up until the first frozen client.
+The multi-hop proof for each channel end will be different and consist of a proof formed starting from each channel
+end up to the frozen client.
+
+The multi-hop proof starts with a chain with a frozen client for the misbehaving chain. However, the frozen client exists
+on the next blockchain in the channel path so the key/value proof is indexed to evaluate on the consensus state holding
+that client state. The client state path requires knowledge of the client id which can be determined from the
+connectionEnd on the misbehaving chain prior to the misbehavior submission.
+
+Once frozen, it is possible for a channel to be unfrozen (reactivated) via governance processes once the misbehavior in
+the channel path has been resolved. However, this process is out-of-protocol.
+
+Example:
+
+Given a multi-hop channel path over connections from chain `A` to chain `E` and misbehaving chain `C`
+
+`A <--> B <--x C x--> D <--> E`
+
+Assume any relayer submits evidence of misbehavior to chain `B` and chain `D` to freeze their respective clients for chain `C`.
+
+A relayer may then provide a multi-hop proof of the frozen client on chain `B` to chain `A` to close the channel on chain `A`, and another relayer (or the same one) may also relay a multi-hop proof of the frozen client on chain `D` to chain `E` to close the channel end on chain `E`.
+
+However, it must also be proven that the frozen client state corresponds to a specific hop in the channel path.
+
+Therefore, a proof of the connection end on chain `B` with counterparty connection end on chain `C` must also be provided along with the client state proof to prove that the `clientID` for the client state matches the `clientID` in the connection end. Furthermore, the `connectionID` for the connection end MUST match the expected ID from the channel's `connectionHops` field.
+
+```typescript
+function chanCloseFrozen(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proofConnection: MultihopProof,
+ proofClientState: MultihopProof,
+ proofHeight: Height) {
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(portIdentifier, channelIdentifier), capability))
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel !== null)
+ hopsLength = channel.connectionHops.length
+ abortTransactionUnless(hopsLength === 1)
+ abortTransactionUnless(channel.state !== CLOSED)
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // lookup connectionID for connectionEnd corresponding to misbehaving chain
+ let connectionIdx = proofConnection.ConnectionProofs.length + 1
+ abortTransactionUnless(connectionIdx < hopsLength)
+ let connectionID = channel.ConnectionHops[connectionIdx]
+ let connectionProofKey = connectionPath(connectionID)
+ let connectionProofValue = proofConnection.KeyProof.Value
+ let frozenConnectionEnd = abortTransactionUnless(Unmarshal(connectionProofValue))
+
+ // the clientID in the connection end must match the clientID for the frozen client state
+ let clientID = frozenConnectionEnd.ClientId
+
+ // truncated connectionHops. e.g. client D on chain C is frozen: A, B, C, D, E -> A, B, C
+ let connectionHops = channel.ConnectionHops[:len(mProof.ConnectionProofs)+1]
+
+ // verify the connection proof
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofConnection,
+ connectionHops,
+ connectionProofKey,
+ connectionProofValue))
+
+
+ // key and value for the frozen client state
+ let clientStateKey = clientStatePath(clientID)
+ let clientStateValue = proofClientState.KeyProof.Value
+ let frozenClientState = abortTransactionUnless(Unmarshal(clientStateValue))
+
+ // ensure client state is frozen by checking FrozenHeight
+ abortTransactionUnless(frozenClientState.FrozenHeight.RevisionHeight !== 0)
+
+ // verify the frozen client state proof
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofConnection,
+ connectionHops,
+ clientStateKey,
+ clientStateValue))
+
+ channel.state = FROZEN
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+}
+```
+
+##### Multihop utility functions
+
+```typescript
+// Return the counterparty connectionHops
+function getCounterPartyHops(proof: CommitmentProof | MultihopProof, lastConnection: ConnectionEnd) string[] {
+
+ let counterpartyHops: string[] = [lastConnection.counterpartyConnectionIdentifier]
+ if typeof(proof) === 'MultihopProof' {
+ for connData in proofs.ConnectionProofs {
+ connectionEnd = abortTransactionUnless(Unmarshal(connData.Value))
+ counterpartyHops.push(connectionEnd.GetCounterparty().GetConnectionID())
+ }
+
+ // reverse the hops so they are ordered from sender --> receiver
+ counterpartyHops = counterpartyHops.reverse()
+ }
+
+ return counterpartyHops
+}
+```
+
+#### Packet flow & handling
+
+
+
+##### A day in the life of a packet
+
+The following sequence of steps must occur for a packet to be sent from module *1* on machine *A* to module *2* on machine *B*, starting from scratch.
+
+The module can interface with the IBC handler through [ICS 25](../ics-025-handler-interface) or [ICS 26](../ics-026-routing-module).
+
+1. Initial client & port setup, in any order
+ 1. Client created on *A* for *B* (see [ICS 2](../ics-002-client-semantics))
+ 1. Client created on *B* for *A* (see [ICS 2](../ics-002-client-semantics))
+ 1. Module *1* binds to a port (see [ICS 5](../ics-005-port-allocation))
+ 1. Module *2* binds to a port (see [ICS 5](../ics-005-port-allocation)), which is communicated out-of-band to module *1*
+1. Establishment of a connection & channel, optimistic send, in order
+ 1. Connection opening handshake started from *A* to *B* by module *1* (see [ICS 3](../ics-003-connection-semantics))
+ 1. Channel opening handshake started from *1* to *2* using the newly created connection (this ICS)
+ 1. Packet sent over the newly created channel from *1* to *2* (this ICS)
+1. Successful completion of handshakes (if either handshake fails, the connection/channel can be closed & the packet timed-out)
+ 1. Connection opening handshake completes successfully (see [ICS 3](../ics-003-connection-semantics)) (this will require participation of a relayer process)
+ 1. Channel opening handshake completes successfully (this ICS) (this will require participation of a relayer process)
+1. Packet confirmation on machine *B*, module *2* (or packet timeout if the timeout height has passed) (this will require participation of a relayer process)
+1. Acknowledgement (possibly) relayed back from module *2* on machine *B* to module *1* on machine *A*
+
+Represented spatially, packet transit between two machines can be rendered as follows:
+
+
+
+##### Sending packets
+
+The `sendPacket` function is called by a module in order to send *data* (in the form of an IBC packet) on a channel end owned by the calling module.
+
+Calling modules MUST execute application logic atomically in conjunction with calling `sendPacket`.
+
+The IBC handler performs the following steps in order:
+
+- Checks that the channel is opened to send packets
+- Checks that the calling module owns the sending port (see [ICS 5](../ics-005-port-allocation))
+- Checks that the timeout height specified has not already passed on the destination chain
+- Increments the send sequence counter associated with the channel
+- Stores a constant-size commitment to the packet data & packet timeout
+- Returns the sequence number of the sent packet
+
+Note that the full packet is not stored in the state of the chain - merely a short hash-commitment to the data & timeout value. The packet data can be calculated from the transaction execution and possibly returned as log output which relayers can index.
+
+```typescript
+function sendPacket(
+ capability: CapabilityKey,
+ sourcePort: Identifier,
+ sourceChannel: Identifier,
+ timeoutHeight: Height,
+ timeoutTimestamp: uint64,
+ data: bytes): uint64 {
+ channel = provableStore.get(channelPath(sourcePort, sourceChannel))
+
+ // check that the channel must be OPEN to send packets;
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state === OPEN)
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+
+ // check if the calling module owns the sending port
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(sourcePort, sourceChannel), capability))
+
+ // disallow packets with a zero timeoutHeight and timeoutTimestamp
+ abortTransactionUnless(timeoutHeight !== 0 || timeoutTimestamp !== 0)
+
+ // check that the timeout height hasn't already passed in the local client tracking the receiving chain
+ latestClientHeight = provableStore.get(clientPath(connection.clientIdentifier)).latestClientHeight()
+ abortTransactionUnless(timeoutHeight === 0 || latestClientHeight < timeoutHeight)
+
+ // increment the send sequence counter
+ sequence = provableStore.get(nextSequenceSendPath(sourcePort, sourceChannel))
+ provableStore.set(nextSequenceSendPath(sourcePort, sourceChannel), sequence+1)
+
+ // store commitment to the packet data & packet timeout
+ provableStore.set(
+ packetCommitmentPath(sourcePort, sourceChannel, sequence),
+ hash(hash(data), timeoutHeight, timeoutTimestamp)
+ )
+
+ // log that a packet can be safely sent
+ emitLogEntry("sendPacket", {
+ sequence: sequence,
+ data: data,
+ timeoutHeight: timeoutHeight,
+ timeoutTimestamp: timeoutTimestamp
+ })
+
+ return sequence
+}
+```
+
+#### Receiving packets
+
+The `recvPacket` function is called by a module in order to receive an IBC packet sent on the corresponding channel end on the counterparty chain.
+
+Atomically in conjunction with calling `recvPacket`, calling modules MUST either execute application logic or queue the packet for future execution.
+
+The IBC handler performs the following steps in order:
+
+- Checks that the channel & connection are open to receive packets
+- Checks that the calling module owns the receiving port
+- Checks that the packet metadata matches the channel & connection information
+- Checks that the packet sequence is the next sequence the channel end expects to receive (for ordered and ordered_allow_timeout channels)
+- Checks that the timeout height and timestamp have not yet passed
+- Checks the inclusion proof of packet data commitment in the outgoing chain's state
+- Optionally (in case channel upgrades and deletion of acknowledgements and packet receipts are implemented): reject any packet with a sequence already used before a successful channel upgrade
+- Sets a store path to indicate that the packet has been received (unordered channels only)
+- Increments the packet receive sequence associated with the channel end (ordered and ordered_allow_timeout channels only)
+
+We pass the address of the `relayer` that signed and submitted the packet to enable a module to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard).
+
+```typescript
+function recvPacket(
+ packet: OpaquePacket,
+ proof: CommitmentProof | MultihopProof,
+ proofHeight: Height,
+ relayer: string): Packet {
+
+ channel = provableStore.get(channelPath(packet.destPort, packet.destChannel))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state === OPEN || (channel.state === FLUSHING) || (channel.state === FLUSHCOMPLETE))
+ counterpartyUpgrade = privateStore.get(counterpartyUpgradePath(packet.destPort, packet.destChannel))
+ // defensive check that ensures chain does not process a packet higher than the last packet sent before
+ // counterparty went into FLUSHING mode. If the counterparty is implemented correctly, this should never abort
+ abortTransactionUnless(counterpartyUpgrade.nextSequenceSend == 0 || packet.sequence < counterpartyUpgrade.nextSequenceSend)
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.destPort, packet.destChannel), capability))
+ abortTransactionUnless(packet.sourcePort === channel.counterpartyPortIdentifier)
+ abortTransactionUnless(packet.sourceChannel === channel.counterpartyChannelIdentifier)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ if (len(channel.connectionHops) > 1) {
+ key = packetCommitmentPath(packet.GetSourcePort(), packet.GetSourceChannel(), packet.GetSequence())
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key,
+ hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyPacketData(
+ proofHeight,
+ proof,
+ packet.sourcePort,
+ packet.sourceChannel,
+ packet.sequence,
+ hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp)
+ ))
+ }
+
+ // do sequence check before any state changes
+ if channel.order == ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT {
+ nextSequenceRecv = provableStore.get(nextSequenceRecvPath(packet.destPort, packet.destChannel))
+ if (packet.sequence < nextSequenceRecv) {
+ // event is emitted even if transaction is aborted
+ emitLogEntry("recvPacket", {
+ data: packet.data
+ timeoutHeight: packet.timeoutHeight,
+ timeoutTimestamp: packet.timeoutTimestamp,
+ sequence: packet.sequence,
+ sourcePort: packet.sourcePort,
+ sourceChannel: packet.sourceChannel,
+ destPort: packet.destPort,
+ destChannel: packet.destChannel,
+ order: channel.order,
+ connection: channel.connectionHops[0]
+ })
+ }
+
+ abortTransactionUnless(packet.sequence === nextSequenceRecv)
+ }
+
+ switch channel.order {
+ case ORDERED:
+ case UNORDERED:
+ abortTransactionUnless(packet.timeoutHeight === 0 || getConsensusHeight() < packet.timeoutHeight)
+ abortTransactionUnless(packet.timeoutTimestamp === 0 || currentTimestamp() < packet.timeoutTimestamp)
+ break;
+
+ case ORDERED_ALLOW_TIMEOUT:
+ // for ORDERED_ALLOW_TIMEOUT, we do not abort on timeout
+ // instead increment next sequence recv and write the sentinel timeout value in packet receipt
+ // then return
+ if (getConsensusHeight() >= packet.timeoutHeight && packet.timeoutHeight != 0) || (currentTimestamp() >= packet.timeoutTimestamp && packet.timeoutTimestamp != 0) {
+ nextSequenceRecv = nextSequenceRecv + 1
+ provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)
+ provableStore.set(
+ packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
+ TIMEOUT_RECEIPT
+ )
+ }
+ return;
+
+ default:
+ // unsupported channel type
+ abortTransactionUnless(false)
+ }
+
+ // REPLAY PROTECTION: in order to free storage, implementations may choose to
+ // delete acknowledgements and packet receipts when a channel upgrade is successfully
+ // completed. In that case, implementations must also make sure that any packet with
+ // a sequence already used before the channel upgrade is rejected. This is needed to
+ // prevent replay attacks (see this PR in ibc-go for an example of how this is achieved:
+ // https://github.com/cosmos/ibc-go/pull/5651).
+
+ // all assertions passed (except sequence check), we can alter state
+
+ switch channel.order {
+ case ORDERED:
+ case ORDERED_ALLOW_TIMEOUT:
+ nextSequenceRecv = nextSequenceRecv + 1
+ provableStore.set(nextSequenceRecvPath(packet.destPort, packet.destChannel), nextSequenceRecv)
+ break;
+
+ case UNORDERED:
+ // for unordered channels we must set the receipt so it can be verified on the other side
+ // this receipt does not contain any data, since the packet has not yet been processed
+ // it's the sentinel success receipt: []byte{0x01}
+ packetReceipt = provableStore.get(packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence))
+ if (packetReceipt != null) {
+ emitLogEntry("recvPacket", {
+ data: packet.data
+ timeoutHeight: packet.timeoutHeight,
+ timeoutTimestamp: packet.timeoutTimestamp,
+ sequence: packet.sequence,
+ sourcePort: packet.sourcePort,
+ sourceChannel: packet.sourceChannel,
+ destPort: packet.destPort,
+ destChannel: packet.destChannel,
+ order: channel.order,
+ connection: channel.connectionHops[0]
+ })
+ }
+
+ abortTransactionUnless(packetReceipt === null)
+ provableStore.set(
+ packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
+ SUCCESSFUL_RECEIPT
+ )
+ break;
+ }
+
+ // log that a packet has been received
+ emitLogEntry("recvPacket", {
+ data: packet.data
+ timeoutHeight: packet.timeoutHeight,
+ timeoutTimestamp: packet.timeoutTimestamp,
+ sequence: packet.sequence,
+ sourcePort: packet.sourcePort,
+ sourceChannel: packet.sourceChannel,
+ destPort: packet.destPort,
+ destChannel: packet.destChannel,
+ order: channel.order,
+ connection: channel.connectionHops[0]
+ })
+
+ // return transparent packet
+ return packet
+}
+```
+
+#### Writing acknowledgements
+
+The `writeAcknowledgement` function is called by a module in order to write data which resulted from processing an IBC packet that the sending chain can then verify, a sort of "execution receipt" or "RPC call response".
+
+Calling modules MUST execute application logic atomically in conjunction with calling `writeAcknowledgement`.
+
+This is an asynchronous acknowledgement, the contents of which do not need to be determined when the packet is received, only when processing is complete. In the synchronous case, `writeAcknowledgement` can be called in the same transaction (atomically) with `recvPacket`.
+
+Acknowledging packets is not required; however, if an ordered channel uses acknowledgements, either all or no packets must be acknowledged (since the acknowledgements are processed in order). Note that if packets are not acknowledged, packet commitments cannot be deleted on the source chain. Future versions of IBC may include ways for modules to specify whether or not they will be acknowledging packets in order to allow for cleanup.
+
+`writeAcknowledgement` *does not* check if the packet being acknowledged was actually received, because this would result in proofs being verified twice for acknowledged packets. This aspect of correctness is the responsibility of the calling module.
+The calling module MUST only call `writeAcknowledgement` with a packet previously received from `recvPacket`.
+
+The IBC handler performs the following steps in order:
+
+- Checks that an acknowledgement for this packet has not yet been written
+- Sets the opaque acknowledgement value at a store path unique to the packet
+
+```typescript
+function writeAcknowledgement(
+ packet: Packet,
+ acknowledgement: bytes) {
+ // acknowledgement must not be empty
+ abortTransactionUnless(len(acknowledgement) !== 0)
+
+ // cannot already have written the acknowledgement
+ abortTransactionUnless(provableStore.get(packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence) === null))
+
+ // write the acknowledgement
+ provableStore.set(
+ packetAcknowledgementPath(packet.destPort, packet.destChannel, packet.sequence),
+ hash(acknowledgement)
+ )
+
+ // log that a packet has been acknowledged
+ emitLogEntry("writeAcknowledgement", {
+ sequence: packet.sequence,
+ timeoutHeight: packet.timeoutHeight,
+ port: packet.destPort,
+ channel: packet.destChannel,
+ timeoutTimestamp: packet.timeoutTimestamp,
+ data: packet.data,
+ acknowledgement
+ })
+}
+```
+
+#### Processing acknowledgements
+
+The `acknowledgePacket` function is called by a module to process the acknowledgement of a packet previously sent by
+the calling module on a channel to a counterparty module on the counterparty chain.
+`acknowledgePacket` also cleans up the packet commitment, which is no longer necessary since the packet has been received and acted upon.
+
+Calling modules MAY atomically execute appropriate application acknowledgement-handling logic in conjunction with calling `acknowledgePacket`.
+
+We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well.
+
+```typescript
+function acknowledgePacket(
+ packet: OpaquePacket,
+ acknowledgement: bytes,
+ proof: CommitmentProof | MultihopProof,
+ proofHeight: Height,
+ relayer: string): Packet {
+
+ // abort transaction unless that channel is open, calling module owns the associated port, and the packet fields match
+ channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
+ abortTransactionUnless(channel !== null)
+ abortTransactionUnless(channel.state === OPEN || channel.state === FLUSHING)
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
+ abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier)
+ abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+ abortTransactionUnless(connection.state === OPEN)
+
+ // verify we sent the packet and haven't cleared it out yet
+ abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+ === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))
+
+ // abort transaction unless correct acknowledgement on counterparty chain
+ if (len(channel.connectionHops) > 1) {
+ key = packetAcknowledgementPath(packet.GetDestPort(), packet.GetDestChannel(), packet.GetSequence())
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key,
+ acknowledgement
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyPacketAcknowledgement(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ packet.sequence,
+ acknowledgement
+ ))
+ }
+
+ // abort transaction unless acknowledgement is processed in order
+ if (channel.order === ORDERED || channel.order == ORDERED_ALLOW_TIMEOUT) {
+ nextSequenceAck = provableStore.get(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel))
+ abortTransactionUnless(packet.sequence === nextSequenceAck)
+ nextSequenceAck = nextSequenceAck + 1
+ provableStore.set(nextSequenceAckPath(packet.sourcePort, packet.sourceChannel), nextSequenceAck)
+ }
+
+ // all assertions passed, we can alter state
+
+ // delete our commitment so we can't "acknowledge" again
+ provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+
+ if channel.state == FLUSHING {
+ upgradeTimeout = privateStore.get(counterpartyUpgradeTimeout(portIdentifier, channelIdentifier))
+ if upgradeTimeout != nil {
+ // counterparty-specified timeout must not have exceeded
+ // if it has, then restore the channel and abort upgrade handshake
+ if (upgradeTimeout.timeoutHeight != 0 && currentHeight() >= upgradeTimeout.timeoutHeight) ||
+ (upgradeTimeout.timeoutTimestamp != 0 && currentTimestamp() >= upgradeTimeout.timeoutTimestamp ) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ } else if pendingInflightPackets(portIdentifier, channelIdentifier) == nil {
+ // if this was the last in-flight packet, then move channel state to FLUSHCOMPLETE
+ channel.state = FLUSHCOMPLETE
+ publicStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+ }
+ }
+ }
+
+ // return transparent packet
+ return packet
+}
+```
+
+##### Acknowledgement Envelope
+
+The acknowledgement returned from the remote chain is defined as arbitrary bytes in the IBC protocol. This data
+may either encode a successful execution or a failure (anything besides a timeout). There is no generic way to
+distinguish the two cases, which requires that any client-side packet visualiser understands every app-specific protocol
+in order to distinguish the case of successful or failed relay. In order to reduce this issue, we offer an additional
+specification for acknowledgement formats, which [SHOULD](https://www.ietf.org/rfc/rfc2119.txt) be used by the
+app-specific protocols.
+
+```proto
+message Acknowledgement {
+ oneof response {
+ bytes result = 21;
+ string error = 22;
+ }
+}
+```
+
+If an application uses a different format for acknowledgement bytes, it MUST not deserialise to a valid protobuf message
+of this format. Note that all packets contain exactly one non-empty field, and it must be result or error. The field
+numbers 21 and 22 were explicitly chosen to avoid accidental conflicts with other protobuf message formats used
+for acknowledgements. The first byte of any message with this format will be the non-ASCII values `0xaa` (result)
+or `0xb2` (error).
+
+#### Timeouts
+
+Application semantics may require some timeout: an upper limit to how long the chain will wait for a transaction to be processed before considering it an error. Since the two chains have different local clocks, this is an obvious attack vector for a double spend - an attacker may delay the relay of the receipt or wait to send the packet until right after the timeout - so applications cannot safely implement naive timeout logic themselves.
+
+Note that in order to avoid any possible "double-spend" attacks, the timeout algorithm requires that the destination chain is running and reachable. One can prove nothing in a complete network partition, and must wait to connect; the timeout must be proven on the recipient chain, not simply the absence of a response on the sending chain.
+
+##### Sending end
+
+The `timeoutPacket` function is called by a module which originally attempted to send a packet to a counterparty module,
+where the timeout height or timeout timestamp has passed on the counterparty chain without the packet being committed, to prove that the packet
+can no longer be executed and to allow the calling module to safely perform appropriate state transitions.
+
+Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutPacket`.
+
+In the case of an ordered channel, `timeoutPacket` checks the `recvSequence` of the receiving channel end and closes the channel if a packet has timed out.
+
+In the case of an unordered channel, `timeoutPacket` checks the absence of the receipt key (which will have been written if the packet was received). Unordered channels are expected to continue in the face of timed-out packets.
+
+If relations are enforced between timeout heights of subsequent packets, safe bulk timeouts of all packets prior to a timed-out packet can be performed. This specification omits details for now.
+
+We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well.
+
+```typescript
+function timeoutPacket(
+ packet: OpaquePacket,
+ proof: CommitmentProof | MultihopProof,
+ proofHeight: Height,
+ nextSequenceRecv: Maybe,
+ relayer: string): Packet {
+
+ channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
+ abortTransactionUnless(channel !== null)
+
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
+ abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(connection !== null)
+
+ // note: the connection may have been closed
+ abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier)
+
+ // get the timestamp from the final consensus state in the channel path
+ var proofTimestamp
+ if (channel.connectionHops.length > 1) {
+ consensusState = abortTransactionUnless(Unmarshal(proof.ConsensusProofs[proof.ConsensusProofs.length-1].Value))
+ proofTimestamp = consensusState.GetTimestamp()
+ } else {
+ proofTimestamp, err = connection.getTimestampAtHeight(connection, proofHeight)
+ }
+
+ // check that timeout height or timeout timestamp has passed on the other end
+ abortTransactionUnless(
+ (packet.timeoutHeight > 0 && proofHeight >= packet.timeoutHeight) ||
+ (packet.timeoutTimestamp > 0 && proofTimestamp >= packet.timeoutTimestamp))
+
+ // verify we actually sent this packet, check the store
+ abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+ === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))
+
+ switch channel.order {
+ case ORDERED:
+ // ordered channel: check that packet has not been received
+ // only allow timeout on next sequence so all sequences before the timed out packet are processed (received/timed out)
+ // before this packet times out
+ abortTransactionUnless(packet.sequence == nextSequenceRecv)
+ // ordered channel: check that the recv sequence is as claimed
+ if (channel.connectionHops.length > 1) {
+ key = nextSequenceRecvPath(packet.srcPort, packet.srcChannel)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key,
+ nextSequenceRecv
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyNextSequenceRecv(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ nextSequenceRecv
+ ))
+ }
+ break;
+
+ case UNORDERED:
+ if (channel.connectionHops.length > 1) {
+ key = packetReceiptPath(packet.srcPort, packet.srcChannel, packet.sequence)
+ abortTransactionUnless(connection.verifyMultihopNonMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key
+ ))
+ } else {
+ // unordered channel: verify absence of receipt at packet index
+ abortTransactionUnless(connection.verifyPacketReceiptAbsence(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ packet.sequence
+ ))
+ }
+ break;
+
+ // NOTE: For ORDERED_ALLOW_TIMEOUT, the relayer must first attempt the receive on the destination chain
+ // before the timeout receipt can be written and subsequently proven on the sender chain in timeoutPacket
+ case ORDERED_ALLOW_TIMEOUT:
+ abortTransactionUnless(packet.sequence == nextSequenceRecv - 1)
+
+ if (channel.connectionHops.length > 1) {
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ packetReceiptPath(packet.destPort, packet.destChannel, packet.sequence),
+ TIMEOUT_RECEIPT
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyPacketReceipt(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ packet.sequence
+ TIMEOUT_RECEIPT,
+ ))
+ }
+ break;
+
+ default:
+ // unsupported channel type
+ abortTransactionUnless(true)
+ }
+
+ // all assertions passed, we can alter state
+
+ // delete our commitment
+ provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+
+ if channel.state == FLUSHING {
+ upgradeTimeout = privateStore.get(counterpartyUpgradeTimeout(portIdentifier, channelIdentifier))
+ if upgradeTimeout != nil {
+ // counterparty-specified timeout must not have exceeded
+ // if it has, then restore the channel and abort upgrade handshake
+ if (upgradeTimeout.timeoutHeight != 0 && currentHeight() >= upgradeTimeout.timeoutHeight) ||
+ (upgradeTimeout.timeoutTimestamp != 0 && currentTimestamp() >= upgradeTimeout.timeoutTimestamp ) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ } else if pendingInflightPackets(portIdentifier, channelIdentifier) == nil {
+ // if this was the last in-flight packet, then move channel state to FLUSHCOMPLETE
+ channel.state = FLUSHCOMPLETE
+ publicStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+ }
+ }
+ }
+
+ // only close on strictly ORDERED channels
+ if channel.order === ORDERED {
+ // if the channel is ORDERED and a packet is timed out in FLUSHING state then
+ // all upgrade information is deleted and the channel is set to CLOSED.
+
+ if channel.State == FLUSHING {
+ // delete auxiliary upgrade state
+ provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier))
+ privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier))
+ }
+
+ // ordered channel: close the channel
+ channel.state = CLOSED
+ provableStore.set(channelPath(packet.sourcePort, packet.sourceChannel), channel)
+ }
+ // on ORDERED_ALLOW_TIMEOUT, increment NextSequenceAck so that next packet can be acknowledged after this packet timed out.
+ if channel.order === ORDERED_ALLOW_TIMEOUT {
+ nextSequenceAck = nextSequenceAck + 1
+ provableStore.set(nextSequenceAckPath(packet.srcPort, packet.srcChannel), nextSequenceAck)
+ }
+
+ // return transparent packet
+ return packet
+}
+```
+
+##### Timing-out on close
+
+The `timeoutOnClose` function is called by a module in order to prove that the channel
+to which an unreceived packet was addressed has been closed, so the packet will never be received
+(even if the `timeoutHeight` or `timeoutTimestamp` has not yet been reached).
+
+Calling modules MAY atomically execute appropriate application timeout-handling logic in conjunction with calling `timeoutOnClose`.
+
+We pass the `relayer` address just as in [Receiving packets](#receiving-packets) to allow for possible incentivization here as well.
+
+```typescript
+function timeoutOnClose(
+ packet: Packet,
+ proof: CommitmentProof | MultihopProof,
+ proofClosed: CommitmentProof | MultihopProof,
+ proofHeight: Height,
+ nextSequenceRecv: Maybe,
+ relayer: string): Packet {
+
+ channel = provableStore.get(channelPath(packet.sourcePort, packet.sourceChannel))
+ // note: the channel may have been closed
+ abortTransactionUnless(authenticateCapability(channelCapabilityPath(packet.sourcePort, packet.sourceChannel), capability))
+ abortTransactionUnless(packet.destChannel === channel.counterpartyChannelIdentifier)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ // note: the connection may have been closed
+ abortTransactionUnless(packet.destPort === channel.counterpartyPortIdentifier)
+
+ // verify we actually sent this packet, check the store
+ abortTransactionUnless(provableStore.get(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+ === hash(packet.data, packet.timeoutHeight, packet.timeoutTimestamp))
+
+ // return hops from counterparty's view
+ counterpartyHops = getCounterpartyHops(proof, connection)
+
+ // check that the opposing channel end has closed
+ expected = ChannelEnd{CLOSED, channel.order, channel.portIdentifier,
+ channel.channelIdentifier, counterpartyHops, channel.version}
+
+ // verify channel is closed
+ if (channel.connectionHops.length > 1) {
+ key = channelPath(counterparty.PortId, counterparty.ChannelId)
+ abortTransactionUnless(connection.VerifyMultihopMembership(
+ connection,
+ proofHeight,
+ proofClosed,
+ channel.ConnectionHops,
+ key,
+ expected
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyChannelState(
+ proofHeight,
+ proofClosed,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ expected
+ ))
+ }
+
+ switch channel.order {
+ case ORDERED:
+ // ordered channel: check that packet has not been received
+ abortTransactionUnless(packet.sequence >= nextSequenceRecv)
+
+ // ordered channel: check that the recv sequence is as claimed
+ if (channel.connectionHops.length > 1) {
+ key = nextSequenceRecvPath(packet.destPort, packet.destChannel)
+ abortTransactionUnless(connection.verifyMultihopMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key,
+ nextSequenceRecv
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyNextSequenceRecv(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ nextSequenceRecv
+ ))
+ }
+ break;
+
+ case UNORDERED:
+ // unordered channel: verify absence of receipt at packet index
+ if (channel.connectionHops.length > 1) {
+ abortTransactionUnless(connection.verifyMultihopNonMembership(
+ connection,
+ proofHeight,
+ proof,
+ channel.ConnectionHops,
+ key
+ ))
+ } else {
+ abortTransactionUnless(connection.verifyPacketReceiptAbsence(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ packet.sequence
+ ))
+ }
+ break;
+
+ case ORDERED_ALLOW_TIMEOUT:
+ // if packet.sequence >= nextSequenceRecv, then the relayer has not attempted
+ // to receive the packet on the destination chain (e.g. because the channel is already closed).
+ // In this situation it is not needed to verify the presence of a timeout receipt.
+ // Otherwise, if packet.sequence < nextSequenceRecv, then the relayer has attempted
+ // to receive the packet on the destination chain, and nextSequenceRecv has been incremented.
+ // In this situation, verify the presence of timeout receipt.
+ if packet.sequence < nextSequenceRecv {
+ abortTransactionUnless(connection.verifyPacketReceipt(
+ proofHeight,
+ proof,
+ packet.destPort,
+ packet.destChannel,
+ packet.sequence
+ TIMEOUT_RECEIPT,
+ ))
+ }
+ break;
+
+ default:
+ // unsupported channel type
+ abortTransactionUnless(true)
+ }
+
+ // all assertions passed, we can alter state
+
+ // delete our commitment
+ provableStore.delete(packetCommitmentPath(packet.sourcePort, packet.sourceChannel, packet.sequence))
+
+ // return transparent packet
+ return packet
+}
+```
+
+##### Cleaning up state
+
+Packets must be acknowledged in order to be cleaned-up.
+
+#### Reasoning about race conditions
+
+##### Simultaneous handshake attempts
+
+If two machines simultaneously initiate channel opening handshakes with each other, attempting to use the same identifiers, both will fail and new identifiers must be used.
+
+##### Identifier allocation
+
+There is an unavoidable race condition on identifier allocation on the destination chain. Modules would be well-advised to utilise pseudo-random, non-valuable identifiers. Managing to claim the identifier that another module wishes to use, however, while annoying, cannot man-in-the-middle a handshake since the receiving module must already own the port to which the handshake was targeted.
+
+##### Timeouts / packet confirmation
+
+There is no race condition between a packet timeout and packet confirmation, as the packet will either have passed the timeout height prior to receipt or not.
+
+##### Man-in-the-middle attacks during handshakes
+
+Verification of cross-chain state prevents man-in-the-middle attacks for both connection handshakes & channel handshakes since all information (source, destination client, channel, etc.) is known by the module which starts the handshake and confirmed prior to handshake completion.
+
+##### Connection / channel closure with in-flight packets
+
+If a connection or channel is closed while packets are in-flight, the packets can no longer be received on the destination chain and can be timed-out on the source chain.
+
+#### Querying channels
+
+Channels can be queried with `queryChannel`:
+
+```typescript
+function queryChannel(connId: Identifier, chanId: Identifier): ChannelEnd | void {
+ return provableStore.get(channelPath(connId, chanId))
+}
+```
+
+### Properties & Invariants
+
+- The unique combinations of channel & port identifiers are first-come-first-serve: once a pair has been allocated, only the modules owning the ports in question can send or receive on that channel.
+- Packets are delivered exactly once, assuming that the chains are live within the timeout window, and in case of timeout can be timed-out exactly once on the sending chain.
+- The channel handshake cannot be man-in-the-middle attacked by another module on either blockchain or another blockchain's IBC handler.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Data structures & encoding can be versioned at the connection or channel level. Channel logic is completely agnostic to packet data formats, which can be changed by the modules any way they like at any time.
+
+## Example Implementations
+
+- Implementation of ICS 04 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 04 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Jun 5, 2019 - Draft submitted
+
+Jul 4, 2019 - Modifications for unordered channels & acknowledgements
+
+Jul 16, 2019 - Alterations for multi-hop routing future compatibility
+
+Jul 29, 2019 - Revisions to handle timeouts after connection closure
+
+Aug 13, 2019 - Various edits
+
+Aug 25, 2019 - Cleanup
+
+Jan 10, 2022 - Add ORDERED_ALLOW_TIMEOUT channel type and appropriate logic
+
+Mar 28, 2023 - Add `writeChannel` function to write channel end after executing application callback
+
+Dec 4, 2024 - Remove the description for optimistic packet sending
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.mdx b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.mdx
new file mode 100644
index 00000000..7c8751de
--- /dev/null
+++ b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/UPGRADES.mdx
@@ -0,0 +1,1116 @@
+---
+title: "Upgrading Channels"
+---
+
+
+## Synopsis
+
+This standard document specifies the interfaces and state machine logic that IBC implementations must implement in order to enable existing channels to upgrade after the initial channel handshake.
+
+## Motivation
+
+As new features get added to IBC, chains may wish to take advantage of new channel features without abandoning the accumulated state and network effect(s) of an already existing channel. The upgrade protocol proposed would allow chains to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing packet state processed on the channel.
+
+## Desired Properties
+
+- Both chains MUST agree to the renegotiated channel parameters.
+- Channel state and logic on both chains SHOULD either be using the old parameters or the new parameters, but MUST NOT be in an in-between state, e.g., it MUST NOT be possible for an application to run v2 logic, while its counterparty is still running v1 logic.
+- The channel upgrade protocol is atomic, i.e.,
+ - either it is unsuccessful and then the channel MUST fall-back to the original channel parameters;
+ - or it is successful and then both channel ends MUST adopt the new channel parameters and the applications must process packet data appropriately.
+- Packets sent under the previously negotiated parameters must be processed under the previously negotiated parameters, packets sent under the newly negotiated parameters must be processed under the newly negotiated parameters. Thus, in-flight packets sent before the upgrade handshake is complete will be processed according to the original parameters.
+- The channel upgrade protocol MUST NOT modify the channel identifiers.
+
+## Technical Specification
+
+### Data Structures
+
+The `ChannelState` and `ChannelEnd` are defined in [ICS-4](./README), they are reproduced here for the reader's convenience. `FLUSHING` and `FLUSHCOMPLETE` are additional states added to enable the upgrade feature.
+
+#### `ChannelState`
+
+```typescript
+enum ChannelState {
+ INIT,
+ TRYOPEN,
+ OPEN,
+ FLUSHING,
+ FLUSHCOMPLETE,
+}
+```
+
+- In `ChanUpgradeInit`, the initializing chain that is proposing the upgrade should store the channel upgrade.
+- The counterparty chain executing `ChanUpgradeTry` that accepts the upgrade should store the channel upgrade, set the channel state from `OPEN` to `FLUSHING`, and start the flushing timer by storing an upgrade timeout.
+- Once the initiating chain verifies the counterparty is in `FLUSHING`, it must also move to `FLUSHING` unless all in-flight packets are already flushed on its end, in which case it must move directly to `FLUSHCOMPLETE`. The initiator will also store the counterparty timeout to ensure it does not move to `FLUSHCOMPLETE` after the counterparty timeout has passed.
+- The counterparty chain must prove that the initiator is also in `FLUSHING` or completed flushing in `FLUSHCOMPLETE`. The counterparty will store the initiator timeout to ensure it does not move to `FLUSHCOMPLETE` after the initiator timeout has passed.
+
+`FLUSHING` is a "blocking" state that prevents a channel end from advancing to `FLUSHCOMPLETE` unless the in-flight packets on its channel end are flushed and both channel ends have already moved to `FLUSHING`. Once both sides have moved to `FLUSHCOMPLETE`, a relayer can prove this on both ends with `ChanUpgradeOpen` to open the channel on both sides with the new parameters.
+
+#### `ChannelEnd`
+
+```typescript
+interface ChannelEnd {
+ state: ChannelState
+ ordering: ChannelOrder
+ counterpartyPortIdentifier: Identifier
+ counterpartyChannelIdentifier: Identifier
+ connectionHops: [Identifier]
+ version: string
+ upgradeSequence: uint64
+}
+```
+
+- `state`: The state is specified by the handshake steps of the upgrade protocol and will be mutated in place during the handshake. It will be in `FLUSHING` mode when the channel end is flushing in-flight packets. The state will change to `FLUSHCOMPLETE` once there are no in-flight packets left and the channelEnd is ready to move to `OPEN`.
+- `upgradeSequence`: The upgrade sequence will be incremented and agreed upon during the upgrade handshake and will be mutated in place.
+
+All other parameters will remain the same during the upgrade handshake until the upgrade handshake completes. When the channel is reset to `OPEN` on a successful upgrade handshake, the fields on the channel end will be switched over to the `UpgradeFields` specified in the `Upgrade`.
+
+#### `UpgradeFields`
+
+```typescript
+interface UpgradeFields {
+ version: string
+ ordering: ChannelOrder
+ connectionHops: [Identifier]
+}
+```
+
+MAY BE MODIFIED:
+
+- `version`: The version MAY be modified by the upgrade protocol. The same version negotiation that happens in the initial channel handshake can be employed for the upgrade handshake.
+- `ordering`: The ordering MAY be modified by the upgrade protocol so long as the new ordering is supported by underlying connection.
+- `connectionHops`: The connectionHops MAY be modified by the upgrade protocol.
+
+MUST NOT BE MODIFIED:
+
+- `counterpartyChannelIdentifier`: The counterparty channel identifier MUST NOT be modified by the upgrade protocol.
+- `counterpartyPortIdentifier`: The counterparty port identifier MUST NOT be modified by the upgrade protocol
+
+NOTE: If the upgrade adds any fields to the `ChannelEnd` these are by default modifiable, and can be arbitrarily chosen by an Actor (e.g. chain governance) which has permission to initiate the upgrade.
+
+#### `Timeout`
+
+```typescript
+interface Timeout {
+ timeoutHeight: Height
+ timeoutTimestamp: uint64
+}
+```
+
+- `timeoutHeight`: Timeout height indicates the height at which the counterparty must no longer proceed with the upgrade handshake. The chains will then preserve their original channel and the upgrade handshake is aborted.
+- `timeoutTimestamp`: Timeout timestamp indicates the time on the counterparty at which the counterparty must no longer proceed with the upgrade handshake. The chains will then preserve their original channel and the upgrade handshake is aborted.
+
+At least one of the `timeoutHeight` or `timeoutTimestamp` MUST be non-zero.
+
+#### `Upgrade`
+
+The upgrade type will represent a particular upgrade attempt on a channel end.
+
+```typescript
+interface Upgrade {
+ fields: UpgradeFields
+ timeout: Timeout
+ nextSequenceSend: uint64
+}
+```
+
+The upgrade contains the proposed upgrade for the channel end on the executing chain, the timeout for the upgrade attempt, and the next packet send sequence for the channel. The `nextSequenceSend` allows the counterparty to know which packets need to be flushed before the channel can reopen with the newly negotiated parameters. Any packet sent to the channel end with a packet sequence greater than or equal to the `nextSequenceSend` will be rejected until the upgrade is complete. The `nextSequenceSend` will also be used to set the new sequences for the counterparty when it opens for a new upgrade.
+
+#### `ErrorReceipt`
+
+```typescript
+interface ErrorReceipt {
+ sequence: uint64
+ errorMsg: string
+}
+```
+
+- `sequence` contains the `upgradeSequence` at which the error occurred.
+- `errorMsg` contains an arbitrary string which chains may use to provide additional information as to why the upgrade was aborted.
+
+### Store Paths
+
+#### Channel Upgrade Path
+
+The chain must store the proposed upgrade upon initiating an upgrade. The proposed upgrade must be stored in the provable store. It may be deleted once the upgrade is successful or has been aborted.
+
+```typescript
+function channelUpgradePath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "channelUpgrades/upgrades/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+```
+
+The upgrade path has an associated membership verification method added to the connection interface so that a counterparty may verify that chain has stored and committed to a particular set of upgrade parameters.
+
+```typescript
+// Connection VerifyChannelUpgrade method
+function verifyChannelUpgrade(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ upgrade: Upgrade
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(
+ connection.counterpartyPrefix,
+ channelUpgradePath(counterpartyPortIdentifier, counterpartyChannelIdentifier)
+ )
+ return verifyMembership(clientState, height, 0, 0, proof, path, upgrade)
+}
+```
+
+#### CounterpartyUpgrade Path
+
+The chain must store the counterparty upgrade on `chanUpgradeAck` and `chanUpgradeConfirm`. This will be stored in the `counterpartyUpgrade` path on the private store.
+
+```typescript
+function counterpartyUpgradePath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "channelUpgrades/counterpartyUpgrade/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+```
+
+#### Upgrade Error Path
+
+The upgrade error path is a public path that can signal an error of the upgrade to the counterparty for the given upgrade attempt. It does not store anything in the successful case, but it will store the `ErrorReceipt` in the case that a chain does not accept the proposed upgrade.
+
+```typescript
+function channelUpgradeErrorPath(portIdentifier: Identifier, channelIdentifier: Identifier): Path {
+ return "channelUpgrades/upgradeError/ports/{portIdentifier}/channels/{channelIdentifier}"
+}
+```
+
+The upgrade error MUST have an associated verification membership and non-membership function added to the connection interface so that a counterparty may verify that chain has stored a non-empty error in the upgrade error path.
+
+```typescript
+// Connection VerifyChannelUpgradeError method
+function verifyChannelUpgradeError(
+ connection: ConnectionEnd,
+ height: Height,
+ proof: CommitmentProof,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ upgradeErrorReceipt: ErrorReceipt
+) {
+ clientState = queryClientState(connection.clientIdentifier)
+ path = applyPrefix(
+ connection.counterpartyPrefix,
+ channelUpgradeErrorPath(counterpartyPortIdentifier, counterpartyChannelIdentifier)
+ )
+ return verifyMembership(clientState, height, 0, 0, proof, path, upgradeErrorReceipt)
+}
+```
+
+## Sub-Protocols
+
+The channel upgrade process consists of the following sub-protocols: `initUpgradeHandshake`, `startFlushUpgradeHandshake`, `openUpgradeHandshake`, `cancelChannelUpgrade`, and `timeoutChannelUpgrade`. In the case where both chains approve of the proposed upgrade, the upgrade handshake protocol should complete successfully and the `ChannelEnd` should upgrade to the new parameters in `OPEN` state.
+
+### Utility Functions
+
+`initUpgradeHandshake` is a sub-protocol that will initialize the channel end for the upgrade handshake. It will validate the upgrade parameters and store the channel upgrade. All packet processing will continue according to the original channel parameters, as this is a signalling mechanism that can remain indefinitely. The new proposed upgrade will be stored in the provable store for counterparty verification. If it is called again before the handshake starts, then the current proposed upgrade will be replaced with the new one and the channel upgrade sequence will be incremented.
+
+```typescript
+// initUpgradeHandshake will verify that the channel is in the
+// correct precondition to call the initUpgradeHandshake protocol.
+// it will verify the new upgrade field parameters, and make the
+// relevant state changes for initializing a new upgrade:
+// - store channel upgrade
+// - incrementing upgrade sequence
+function initUpgradeHandshake(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proposedUpgradeFields: UpgradeFields,
+): uint64 {
+ // current channel must be OPEN
+ // If channel already has an upgrade but isn't in FLUSHING,
+ // then this will override the previous upgrade attempt
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === OPEN)
+
+ // new channel version must be nonempty
+ abortTransactionUnless(proposedUpgradeFields.Version !== "")
+
+ // proposedConnection must exist and be in OPEN state for
+ // channel upgrade to be accepted
+ proposedConnection = provableStore.get(connectionPath(proposedUpgradeFields.connectionHops[0]))
+ abortTransactionUnless(proposedConnection !== null && proposedConnection.state === OPEN)
+
+ // new order must be supported by the new connection
+ abortTransactionUnless(isSupported(proposedConnection, proposedUpgradeFields.ordering))
+
+ // nextSequenceSend and timeout will be filled when we move to FLUSHING
+ upgrade = Upgrade{
+ fields: proposedUpgradeFields,
+ }
+
+ // store upgrade in provable store for counterparty proof verification
+ provableStore.set(channelUpgradePath(portIdentifier, channelIdentifier), upgrade)
+
+ channel.upgradeSequence = channel.upgradeSequence + 1
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+ return channel.upgradeSequence
+}
+```
+
+`isCompatibleUpgradeFields` will return true if two upgrade field structs are mutually compatible as counterparties, and false otherwise. The first field must be the upgrade fields on the executing chain, the second field must be the counterparty upgrade fields. This function will also check that the proposed connection hops exists, is `OPEN`, and is mutually compatible with the counterparty connection hops.
+
+```typescript
+function isCompatibleUpgradeFields(
+ proposedUpgradeFields: UpgradeFields,
+ counterpartyUpgradeFields: UpgradeFields,
+): boolean {
+ if (proposedUpgradeFields.ordering != counterpartyUpgradeFields.ordering) {
+ return false
+ }
+ if (proposedUpgradeFields.version != counterpartyUpgradeFields.version) {
+ return false
+ }
+
+ // connectionHops can change in a channel upgrade, however both sides must
+ // still be each other's counterparty. Since connection hops may be provided
+ // by relayer, we will abort to avoid changing state based on relayer-provided value
+ // Note: If the proposed connection came from an existing upgrade, then the
+ // off-chain authority is responsible for replacing one side's upgrade fields
+ // to be compatible so that the upgrade handshake can proceed
+ proposedConnection = provableStore.get(connectionPath(proposedUpgradeFields.connectionHops[0]))
+ if (proposedConnection == null || proposedConnection.state != OPEN) {
+ return false
+ }
+ if (counterpartyUpgradeFields.connectionHops[0] != proposedConnection.counterpartyConnectionIdentifier) {
+ return false
+ }
+ return true
+}
+```
+
+`startFlushUpgradeHandshake` will block the upgrade from continuing until all in-flight packets have been flushed. It will set the channel state to `FLUSHING` and block `sendPacket`. During this time; `receivePacket`, `acknowledgePacket` and `timeoutPacket` will still be allowed and processed according to the original channel parameters. The state machine will set a timer for how long the other side can take before it completes flushing and moves to `FLUSHCOMPLETE`. The new proposed upgrade will be stored in the public store for counterparty verification.
+
+```typescript
+// startFlushUpgradeHandshake will verify that the channel
+// is in a valid precondition for calling the startFlushUpgradeHandshake.
+// it will set the channel to flushing state.
+// it will store the nextSequenceSend and upgrade timeout in the upgrade state.
+function startFlushUpgradeHandshake(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === OPEN)
+
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(upgrade !== null)
+
+ channel.state = FLUSHING
+
+ upgradeTimeout = getUpgradeTimeout(channel.portIdentifier, channel.channelIdentifier)
+ // either timeout height or timestamp must be non-zero
+ abortTransactionUnless(upgradeTimeout.timeoutHeight != 0 || upgradeTimeout.timeoutTimestamp != 0)
+
+ nextSequenceSend = provableStore.get(nextSequenceSendPath(portIdentifier, channelIdentifier))
+
+ upgrade.timeout = upgradeTimeout
+ upgrade.nextSequenceSend = nextSequenceSend
+
+ // store upgrade in public store for counterparty proof verification
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+ provableStore.set(channelUpgradePath(portIdentifier, channelIdentifier), upgrade)
+}
+```
+
+`openUpgradeHandshake` will open the channel and switch the existing channel parameters to the newly agreed-upon upgraded channel fields.
+
+```typescript
+// openUpgradeHandshake will switch the channel fields
+// over to the agreed upon upgrade fields.
+// it will reset the channel state to OPEN.
+// it will delete auxiliary upgrade state.
+// caller must do all relevant checks before calling this function.
+function openUpgradeHandshake(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+
+ // if channel order changed, we need to set
+ // the recv and ack sequences appropriately
+ if channel.order == "UNORDERED" && upgrade.fields.ordering == "ORDERED" {
+ selfNextSequenceSend = provableStore.get(nextSequenceSendPath(portIdentifier, channelIdentifier))
+ counterpartyUpgrade = privateStore.get(counterpartyUpgradePath(portIdentifier, channelIdentifier))
+
+ // set nextSequenceRecv to the counterparty nextSequenceSend since all packets were flushed
+ provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), counterpartyUpgrade.nextSequenceSend)
+ // set nextSequenceAck to our own nextSequenceSend since all packets were flushed
+ provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), selfNextSequenceSend)
+ } else if channel.order == "ORDERED" && upgrade.fields.ordering == "UNORDERED" {
+ // reset recv and ack sequences to 1 for UNORDERED channel
+ provableStore.set(nextSequenceRecvPath(portIdentifier, channelIdentifier), 1)
+ provableStore.set(nextSequenceAckPath(portIdentifier, channelIdentifier), 1)
+ }
+
+ // switch channel fields to upgrade fields
+ // and set channel state to OPEN
+ channel.ordering = upgrade.fields.ordering
+ channel.version = upgrade.fields.version
+ channel.connectionHops = upgrade.fields.connectionHops
+ channel.state = OPEN
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+
+ // IMPLEMENTATION DETAIL: Implementations may choose to prune stale acknowledgements and receipts at this stage
+ // Since flushing has completed, any acknowledgement or receipt written before the chain went into flushing has
+ // already been processed by the counterparty and can be removed.
+ // Implementations may do this pruning work over multiple blocks for gas reasons. In this case, they should be sure
+ // to only prune stale acknowledgements/receipts and not new ones that have been written after the channel has reopened.
+ // Implementations may use the counterparty NextSequenceSend as a way to determine which acknowledgement/receipts
+ // were already processed by counterparty when flushing completed
+
+ // delete auxiliary state
+ provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier))
+ privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier))
+}
+```
+
+`restoreChannel` will write an `ErrorReceipt`, set the channel back to its original state and delete upgrade information when the executing channel needs to abort the upgrade handshake and return to the original parameters.
+
+```typescript
+// restoreChannel will restore the channel state to its pre-upgrade state
+// and delete upgrade auxiliary state so that upgrade is aborted.
+// it writes an error receipt to state so counterparty can restore as well.
+// NOTE: this function signature may be modified by implementers to take a custom error
+function restoreChannel(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+) {
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ errorReceipt = ErrorReceipt{
+ channel.upgradeSequence,
+ "upgrade handshake is aborted", // constant string changeable by implementation
+ }
+ provableStore.set(channelUpgradeErrorPath(portIdentifier, channelIdentifier), errorReceipt)
+ channel.state = OPEN
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+
+ // delete auxiliary state
+ provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier))
+ privateStore.delete(counterpartyUpgradePath(portIdentifier, channelIdentifier))
+}
+```
+
+`pendingInflightPackets` will return the list of in-flight packet sequences sent from this `ChannelEnd`. This can be monitored since the packet commitments are deleted when the packet lifecycle is complete. Thus if the packet commitment exists on the sender chain, the packet lifecycle is incomplete. The pseudocode is not provided in this spec since it will be dependent on the state machine in-question. The ibc-go implementation will use the store iterator to implement this functionality. The function signature is provided below:
+
+```typescript
+// pendingInflightPacketSequences returns the packet sequences sent on
+// this end that have not had their lifecycle completed
+function pendingInflightPacketSequences(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier
+): [uint64]
+```
+
+`isAuthorizedUpgrader` will return true if the provided address is authorized to initialize, modify, and cancel upgrades. Chains may permission a set of addresses that can signal which upgrade a channel is willing to upgrade to.
+
+```typescript
+// isAuthorizedUpgrader
+function isAuthorizedUpgrader(address: string): boolean
+```
+
+`getUpgradeTimeout` will return the upgrade timeout specified for the given channel. This may be a chain-wide parameter, or it can be a parameter chosen per channel. This is an implementation-level detail, so only the function signature is specified here. Note this should retrieve some stored timeout delta for the channel and add it to the current height and time to get the absolute timeout values.
+
+```typescript
+// getUpgradeTimeout
+function getUpgradeTimeout(portIdentifier: string, channelIdentifier: string) Timeout {
+}
+```
+
+### Upgrade Handshake
+
+The upgrade handshake defines seven datagrams: *ChanUpgradeInit*, *ChanUpgradeTry*, *ChanUpgradeAck*, *ChanUpgradeConfirm*, *ChanUpgradeOpen*, *ChanUpgradeTimeout*, and *ChanUpgradeCancel*
+
+A successful protocol execution flows as follows (note that all calls are made through modules per [ICS 25](../ics-025-handler-interface)):
+
+| Initiator | Datagram | Chain acted upon | Prior state (A, B) | Posterior state (A, B) |
+| --------- | -------------------- | ---------------- | ---------------------------------- | ----------------------------------------------------- |
+| Actor | `ChanUpgradeInit` | A | (OPEN, OPEN) | (OPEN, OPEN) |
+| Relayer | `ChanUpgradeTry` | B | (OPEN, OPEN) | (OPEN, FLUSHING) |
+| Relayer | `ChanUpgradeAck` | A | (OPEN, FLUSHING) | (FLUSHING/FLUSHCOMPLETE, FLUSHING) |
+| Relayer | `ChanUpgradeConfirm` | B | (FLUSHING/FLUSHCOMPLETE, FLUSHING) | (FLUSHING/FLUSHCOMPLETE, FLUSHING/FLUSHCOMPLETE/OPEN) |
+
+**IMPORTANT:** Note it is important that the prior state before the channel upgrade process starts is that **both** channel ends are `OPEN`. Authorized upgraders are at risk of having the channel halt during the upgrade process if the prior state before channel upgrades on one of the ends is not `OPEN`.
+
+Refer to the diagram below for a possible channel upgrade flow. Multiple channel states are shown on steps 5 and 7 where the channel end can move to either one of those possible states upon executing the handshake. Note that in this example, the channel end on chain B moves to `OPEN` with the new parameters on `ChanUpgradeConfirm` (step 7).
+
+
+
+Once both states are in `FLUSHING` and both sides have stored each others upgrade timeouts, both sides can move to `FLUSHCOMPLETE` by clearing their in-flight packets. Once both sides have complete flushing, a relayer may submit a `ChanUpgradeOpen` datagram to both ends proving that the counterparty has also completed flushing in order to move the channelEnd to `OPEN`.
+
+`ChanUpgradeOpen` is only necessary to call on chain B if the chain was not moved to `OPEN` on `ChanUpgradeConfirm` which may happen if all packets on both ends are already flushed.
+
+At the end of a successful upgrade handshake between two chains implementing the sub-protocol, the following properties hold:
+
+- Each chain is running their new upgraded channel end and is processing upgraded logic and state according to the upgraded parameters.
+- Each chain has knowledge of and has agreed to the counterparty's upgraded channel parameters.
+- All packets sent before the handshake have been completely flushed (acked or timed out) with the old parameters.
+- All packets sent after a channel end moves to OPEN will either timeout using new parameters on sending channelEnd or will be received by the counterparty using new parameters.
+
+If a chain does not agree to the proposed counterparty upgraded `ChannelEnd`, it may abort the upgrade handshake by writing an `ErrorReceipt` into the `channelUpgradeErrorPath` and restoring the original channel. The `ErrorReceipt` must contain the current upgrade sequence on the erroring chain's channel end.
+
+`channelUpgradeErrorPath(portID, channelID) => ErrorReceipt(sequence, msg)`
+
+A relayer may then submit a `ChanUpgradeCancel` datagram to the counterparty. Upon receiving this message a chain must verify that the counterparty wrote an `ErrorReceipt` into its `channelUpgradeErrorPath` with a sequence greater than or equal to its own `ChannelEnd`'s upgrade sequence. If successful, it will restore its original channel as well, thus cancelling the upgrade.
+
+If a chain does not reach `FLUSHCOMPLETE` within the counterparty specified timeout, then it MUST NOT move to `FLUSHCOMPLETE` and should instead abort the upgrade. A relayer may submit a proof of this to the counterparty chain in a `ChanUpgradeTimeout` datagram so that counterparty cancels the upgrade and restores its original channel as well.
+
+```typescript
+// Channel Ends on both sides **must** be OPEN before this function is called
+// It is the responsibility of the authorized upgrader to ensure this is the case
+function chanUpgradeInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proposedUpgradeFields: UpgradeFields,
+ msgSender: string,
+) {
+ // chanUpgradeInit may only be called by addresses authorized by executing chain
+ abortTransactionUnless(isAuthorizedUpgrader(msgSender))
+
+ // if a previous upgrade attempt exists, then delete it and write error receipt, so
+ // counterparty can abort it and move to next upgrade
+ existingUpgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ if existingUpgrade != null {
+ provableStore.delete(channelUpgradePath(portIdentifier, channelIdentifier))
+ errorReceipt = ErrorReceipt{
+ channel.upgradeSequence,
+ "abort the previous upgrade attempt so counterparty can accept the new one", // constant string changeable by implementation
+ }
+ provableStore.set(channelUpgradeErrorPath(portIdentifier, channelIdentifier), errorReceipt)
+ }
+
+ upgradeSequence = initUpgradeHandshake(portIdentifier, channelIdentifier, proposedUpgradeFields)
+
+ // call modules onChanUpgradeInit callback
+ // onChanUpgradeInit may return a new proposed version
+ // if an error is returned the upgrade is not written
+ // the callback MUST NOT write state, as all state transitions will occur once
+ // the channel upgrade is complete.
+ module = lookupModule(portIdentifier)
+ version, err = module.onChanUpgradeInit(
+ portIdentifier,
+ channelIdentifier,
+ upgradeSequence,
+ proposedUpgradeFields.ordering,
+ proposedUpgradeFields.connectionHops,
+ proposedUpgradeFields.version
+ )
+ // abort transaction if callback returned error
+ abortTransactionUnless(err === null)
+
+ // replace channel upgrade version with the version returned by application
+ // in case it was modified
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ upgrade.fields.version = version
+ provableStore.set(channelUpgradePath(portIdentifier, channelIdentifier), upgrade)
+}
+```
+
+NOTE: It is up to individual implementations how they will provide access-control to the `chanUpgradeInit` function. E.g. chain governance, permissioned actor, DAO, etc.
+
+```typescript
+function chanUpgradeTry(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyUpgrade: Upgrade,
+ counterpartyUpgradeSequence: uint64,
+ proposedConnectionHops: [Identifier],
+ proofChannel: CommitmentProof,
+ proofUpgrade: CommitmentProof,
+ proofHeight: Height
+) {
+ // current channel must be OPEN (i.e. not in FLUSHING)
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === OPEN)
+
+ // construct counterpartyChannel from existing information and provided
+ // counterpartyUpgradeSequence
+ counterpartyChannel = ChannelEnd{
+ state: OPEN,
+ ordering: channel.ordering,
+ counterpartyPortIdentifier: portIdentifier,
+ counterpartyChannelIdentifier: channelIdentifier,
+ connectionHops: counterpartyHops,
+ version: channel.version,
+ sequence: counterpartyUpgradeSequence,
+ }
+
+ // verify proofs of counterparty state
+ abortTransactionUnless(
+ verifyChannelState(
+ connection,
+ proofHeight,
+ proofChannel,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyChannel
+ )
+ )
+ abortTransactionUnless(
+ verifyChannelUpgrade(
+ connection,
+ proofHeight,
+ proofUpgrade,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyUpgrade
+ )
+ )
+
+ existingUpgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ if existingUpgrade != null {
+ expectedUpgradeSequence = channel.UpgradeSequence
+ } else {
+ // at the end of the TRY step, the current upgrade sequence will be incremented in the non-crossing
+ // hello case due to calling chanUpgradeInit, we should use this expected upgrade sequence for
+ // sequence mismatch comparison
+ expectedUpgradeSequence = channel.UpgradeSequence + 1
+ }
+
+ // NON CROSSING HELLO CASE:
+ // if the counterparty sequence is less than or equal to the current sequence,
+ // then either the counterparty chain is out-of-sync or the message
+ // is out-of-sync and we write an error receipt with our sequence
+ // so that the counterparty can abort their attempt and resync with our sequence.
+ // When the next upgrade attempt is initiated, both sides will move to a fresh
+ // never-before-seen sequence number
+ // CROSSING HELLO CASE:
+ // if the counterparty sequence is less than the current sequence,
+ // then either the counterparty chain is out-of-sync or the message
+ // is out-of-sync and we write an error receipt with our sequence minus one
+ // so that the counterparty can update their sequence as well.
+ // This will cause the outdated counterparty to upgrade the sequence
+ // and abort their out-of-sync upgrade without aborting our own since
+ // the error receipt sequence is lower than ours and higher than the counterparty.
+ if counterpartyUpgradeSequence < expectedUpgradeSequence {
+ errorReceipt = ErrorReceipt{
+ expectedUpgradeSequence - 1,
+ "sequence out of sync", // constant string changeable by implementation
+ }
+ provableStore.set(channelUpgradeErrorPath(portIdentifier, channelIdentifier), errorReceipt)
+ return
+ }
+
+ // create upgrade fields for this chain from counterparty upgrade and
+ // relayer-provided information version may be mutated by application callback
+ upgradeFields = Upgrade{
+ ordering: counterpartyUpgrade.fields.ordering,
+ connectionHops: proposedConnectionHops,
+ version: counterpartyUpgrade.fields.version,
+ }
+
+ // current upgrade either doesn't exist (non-crossing hello case),
+ // we initialize the upgrade with constructed upgradeFields
+ // if it does exist, we are in crossing hellos and must assert
+ // that the upgrade fields are the same for crossing-hellos case
+ if (existingUpgrade == null) {
+ initUpgradeHandshake(portIdentifier, channelIdentifier, upgradeFields)
+ } else {
+ // we must use the existing upgrade fields
+ upgradeFields = existingUpgrade.fields
+ }
+
+ abortTransactionUnless(isCompatibleUpgradeFields(upgradeFields, counterpartyUpgradeFields))
+
+ // if the counterparty sequence is greater than the current sequence,
+ // we fast forward to the counterparty sequence so that both channel
+ // ends are using the same sequence for the current upgrade.
+ // initUpgradeHandshake will increment the sequence so after that call
+ // both sides will have the same upgradeSequence
+ if (counterpartyUpgradeSequence > channel.upgradeSequence) {
+ channel.upgradeSequence = counterpartyUpgradeSequence
+ }
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+
+ // get counterpartyHops for given connection
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ counterpartyHops = [connection.counterpartyConnectionIdentifier]
+
+ // call startFlushUpgradeHandshake to move channel to FLUSHING, which will block
+ // upgrade from progressing to OPEN until flush completes on both ends
+ startFlushUpgradeHandshake(portIdentifier, channelIdentifier)
+
+ // refresh channel to get latest state
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+
+ // call modules onChanUpgradeTry callback
+ // onChanUpgradeTry may return a new proposed version
+ // if an error is returned the upgrade is not written
+ // the callback MUST NOT write state, as all state transitions will occur once
+ // the channel upgrade is complete.
+ module = lookupModule(portIdentifier)
+ version, err = module.onChanUpgradeTry(
+ portIdentifier,
+ channelIdentifier,
+ channel.upgradeSequence,
+ upgradeFields.ordering,
+ upgradeFields.connectionHops,
+ upgradeFields.version
+ )
+ // abort the transaction if the callback returns an error and
+ // there was no existing upgrade. This will allow the counterparty upgrade
+ // to continue existing while this chain may add support for it in the future
+ abortTransactionUnless(err === null)
+
+ // replace channel version with the version returned by application
+ // in case it was modified
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ upgrade.fields.version = version
+ provableStore.set(channelUpgradePath(portIdentifier, channelIdentifier), upgrade)
+}
+```
+
+NOTE: Implementations that want to explicitly permission upgrades should enforce crossing hellos. i.e. Both parties must have called `ChanUpgradeInit` with mutually compatible parameters in order for `ChanUpgradeTry` to succeed. Implementations that want to be permissive towards counterparty-initiated upgrades may allow moving from `OPEN` to `FLUSHING` without having an upgrade previously stored on the executing chain.
+
+```typescript
+function chanUpgradeAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyUpgrade: Upgrade,
+ proofChannel: CommitmentProof,
+ proofUpgrade: CommitmentProof,
+ proofHeight: Height
+) {
+ // current channel is OPEN or FLUSHING (crossing hellos)
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state == OPEN || channel.state == FLUSHING)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ counterpartyHops = [connection.counterpartyConnectionIdentifier]
+
+ // construct counterpartyChannel from existing information
+ counterpartyChannel = ChannelEnd{
+ state: FLUSHING,
+ ordering: channel.ordering,
+ counterpartyPortIdentifier: portIdentifier,
+ counterpartyChannelIdentifier: channelIdentifier,
+ connectionHops: counterpartyHops,
+ version: channel.version,
+ sequence: channel.upgradeSequence,
+ }
+
+ // verify proofs of counterparty state
+ abortTransactionUnless(
+ verifyChannelState(
+ connection,
+ proofHeight,
+ proofChannel,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyChannel
+ )
+ )
+ abortTransactionUnless(
+ verifyChannelUpgrade(
+ connection,
+ proofHeight,
+ proofUpgrade,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyUpgrade
+ )
+ )
+
+ existingUpgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+
+ // optimistically accept version that TRY chain proposes and pass this to callback for confirmation.
+ // in the crossing hello case, we do not modify version that our TRY call returned and instead
+ // enforce that both TRY calls returned the same version
+ if (channel.state == OPEN) {
+ existingUpgrade.fields.version == counterpartyUpgrade.fields.version
+ }
+ // if upgrades are not compatible by ACK step, then we restore the channel
+ if (!isCompatibleUpgradeFields(existingUpgrade.fields, counterpartyUpgrade.fields)) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ return
+ }
+
+ if (channel.state == OPEN) {
+ // prove counterparty and move our own state to flushing
+ // if we are already at flushing, then no state changes occur
+ // upgrade is blocked on this channelEnd from progressing until flush completes on its end
+ startFlushUpgradeHandshake(portIdentifier, channelIdentifier)
+ // startFlushUpgradeHandshake sets the timeout for the upgrade
+ // so retrieve upgrade again here and use that timeout value
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ existingUpgrade.timeout = upgrade.timeout
+ }
+
+ timeout = counterpartyUpgrade.timeout
+
+ // counterparty-specified timeout must not have exceeded
+ // if it has, then restore the channel and abort upgrade handshake
+ if ((timeout.timeoutHeight != 0 && currentHeight() >= timeout.timeoutHeight) ||
+ (timeout.timeoutTimestamp != 0 && currentTimestamp() >= timeout.timeoutTimestamp )) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ return
+ }
+
+ // if there are no in-flight packets on our end, we can automatically go to FLUSHCOMPLETE
+ if (pendingInflightPackets(portIdentifier, channelIdentifier) == null) {
+ channel.state = FLUSHCOMPLETE
+ }
+ // set counterparty upgrade
+ privateStore.set(counterpartyUpgradePath(portIdentifier, channelIdentifier), counterpartyUpgrade)
+
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+
+ // call modules onChanUpgradeAck callback
+ // module can error on counterparty version
+ // ACK should not change state to the new parameters yet
+ // as that will happen on the onChanUpgradeOpen callback
+ module = lookupModule(portIdentifier)
+ err = module.onChanUpgradeAck(
+ portIdentifier,
+ channelIdentifier,
+ counterpartyUpgrade.fields.version
+ )
+ // restore channel if callback returned error
+ if (err != null) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ return
+ }
+
+ // if no error, agree on final version
+ provableStore.set(channelUpgradePath(portIdentifier, channelIdentifier), existingUpgrade)
+}
+```
+
+`chanUpgradeConfirm` is called on the chain which is on `FLUSHING` **after** `chanUpgradeAck` is called on the counterparty. This will inform the TRY chain of the timeout set on ACK by the counterparty. If the timeout has already exceeded, we will write an error receipt and restore. If packets on both sides have already been flushed and timeout is not exceeded, then we can open the channel. Otherwise, we set the counterparty timeout in the private store and wait for packet flushing to complete.
+
+```typescript
+function chanUpgradeConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelState: state,
+ counterpartyUpgrade: Upgrade,
+ proofChannel: CommitmentProof,
+ proofUpgrade: CommitmentProof,
+ proofHeight: Height,
+) {
+ // current channel is in FLUSHING
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === FLUSHING)
+
+ // counterparty channel is either FLUSHING or FLUSHCOMPLETE
+ abortTransactionUnless(counterpartyChannelState === FLUSHING || counterpartyChannelState === FLUSHCOMPLETE)
+
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ counterpartyHops = [connection.counterpartyConnectionIdentifier]
+
+ counterpartyChannel = ChannelEnd{
+ state: counterpartyChannelState,
+ ordering: channel.ordering,
+ counterpartyPortIdentifier: portIdentifier,
+ counterpartyChannelIdentifier: channelIdentifier,
+ connectionHops: counterpartyHops,
+ version: channel.version,
+ sequence: channel.upgradeSequence,
+ }
+
+ // verify proofs of counterparty state
+ abortTransactionUnless(
+ verifyChannelState(
+ connection,
+ proofHeight,
+ proofChannel,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyChannel
+ )
+ )
+ abortTransactionUnless(
+ verifyChannelUpgrade(
+ connection,
+ proofHeight,
+ proofUpgrade,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyUpgrade
+ )
+ )
+
+ existingUpgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+
+ // in the crossing-hello case it is possible that both chains execute the
+ // INIT, TRY and CONFIRM steps without any of them executing ACK, therefore
+ // we also need to check that the upgrades are compatible on this step
+ if (!isCompatibleUpgradeFields(existingUpgrade.fields, counterpartyUpgrade.fields)) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ return
+ }
+
+ timeout = counterpartyUpgrade.timeout
+
+ // counterparty-specified timeout must not have exceeded
+ // if it has, then restore the channel and abort upgrade handshake
+ if ((timeout.timeoutHeight != 0 && currentHeight() >= timeout.timeoutHeight) ||
+ (timeout.timeoutTimestamp != 0 && currentTimestamp() >= timeout.timeoutTimestamp)) {
+ restoreChannel(portIdentifier, channelIdentifier)
+ return
+ }
+
+ // if there are no in-flight packets on our end, we can automatically go to FLUSHCOMPLETE
+ if (pendingInflightPackets(portIdentifier, channelIdentifier) == null) {
+ channel.state = FLUSHCOMPLETE
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+ }
+ // set counterparty upgrade
+ privateStore.set(counterpartyUpgradePath(portIdentifier, channelIdentifier), counterpartyUpgrade)
+
+ // if both chains are already in flushcomplete we can move to OPEN
+ if (channel.state == FLUSHCOMPLETE && counterpartyChannelState == FLUSHCOMPLETE) {
+ openUpgradeHandshake(portIdentifier, channelIdentifier)
+ // make application state changes based on new channel parameters
+ module.onChanUpgradeOpen(portIdentifier, channelIdentifier)
+ }
+}
+```
+
+`chanUpgradeOpen` may only be called once both sides have moved to `FLUSHCOMPLETE`. If there exists unprocessed packets in the queue when the handshake goes into `FLUSHING` mode, then the packet handlers must move the channel end to `FLUSHCOMPLETE` once the last packet on the channel end has been processed.
+
+```typescript
+function chanUpgradeOpen(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelState: ChannelState,
+ counterpartyUpgradeSequence: uint64,
+ proofChannel: CommitmentProof,
+ proofHeight: Height,
+) {
+ // channel must have completed flushing
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === FLUSHCOMPLETE)
+
+ // get connection for proof verification
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+
+ // counterparty must be in OPEN or FLUSHCOMPLETE state
+ if (counterpartyChannelState == OPEN) {
+ // get upgrade since counterparty should have upgraded to these parameters
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+
+ // get the counterparty's connection hops for the proposed upgrade connection
+ proposedConnection = provableStore.get(connectionPath(upgrade.fields.connectionHops))
+ counterpartyHops = [proposedConnection.counterpartyConnectionIdentifier]
+
+ // The counterparty upgrade sequence must be greater than or equal to
+ // the channel upgrade sequence. It should normally be equivalent, but
+ // in the unlikely case that a new upgrade is initiated after it reopens,
+ // then the upgrade sequence will be greater than our upgrade sequence.
+ abortTransactionUnless(counterpartyUpgradeSequence >= channel.upgradeSequence)
+
+ counterpartyChannel = ChannelEnd{
+ state: OPEN,
+ ordering: upgrade.fields.ordering,
+ counterpartyPortIdentifier: portIdentifier,
+ counterpartyChannelIdentifier: channelIdentifier,
+ connectionHops: counterpartyHops,
+ version: upgrade.fields.version,
+ sequence: counterpartyUpgradeSequence,
+ }
+ } else if (counterpartyChannelState == FLUSHCOMPLETE) {
+ counterpartyHops = [connection.counterpartyConnectionIdentifier]
+ counterpartyChannel = ChannelEnd{
+ state: FLUSHCOMPLETE,
+ ordering: channel.ordering,
+ counterpartyPortIdentifier: portIdentifier,
+ counterpartyChannelIdentifier: channelIdentifier,
+ connectionHops: counterpartyHops,
+ version: channel.version,
+ sequence: channel.upgradeSequence,
+ }
+ } else {
+ abortTransactionUnless(false)
+ }
+
+ abortTransactionUnless(
+ verifyChannelState(
+ connection,
+ proofHeight,
+ proofChannel,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyChannel
+ )
+ )
+
+ // move channel to OPEN and adopt upgrade parameters
+ openUpgradeHandshake(portIdentifier, channelIdentifier)
+
+ // call modules onChanUpgradeOpen callback
+ module = lookupModule(portIdentifier)
+ // open callback must not return error since counterparty successfully upgraded
+ // make application state changes based on new channel parameters
+ module.onChanUpgradeOpen(
+ portIdentifier,
+ channelIdentifier
+ )
+}
+```
+
+### Cancel Upgrade Process
+
+During the upgrade handshake a chain may cancel the upgrade by writing an error receipt into the upgrade error path and restoring the original channel to `OPEN`. The counterparty must then restore its channel to `OPEN` as well. A relayer can facilitate this by sending `ChanUpgradeCancel` datagram to the handler:
+
+```typescript
+function cancelChannelUpgrade(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ errorReceipt: ErrorReceipt,
+ proofUpgradeError: CommitmentProof,
+ proofHeight: Height,
+ msgSender: string,
+) {
+ // current channel has an upgrade stored
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(upgrade !== null)
+
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ // if the msgSender is authorized to make and cancel upgrades AND
+ // the current channel has not already reached FLUSHCOMPLETE,
+ // then we can restore immediately without any additional checks
+ // otherwise, we can only cancel if the counterparty wrote an
+ // error receipt during the upgrade handshake
+ if (!(isAuthorizedUpgrader(msgSender) && channel.state != FLUSHCOMPLETE)) {
+ abortTransactionUnless(!isEmpty(errorReceipt))
+
+ if channel.state == FLUSHCOMPLETE {
+ // if the channel state is in FLUSHCOMPLETE, it can **only** be aborted if there
+ // is an error receipt with the exact same sequence. This ensures that the counterparty
+ // did not successfully upgrade and then cancel at a new upgrade to abort our own end,
+ // leading to both channel ends being OPEN with different parameters
+ abortTransactionUnless(errorReceipt.sequence == channel.upgradeSequence)
+ } else {
+ // If counterparty sequence is less than the current sequence,
+ // abort transaction since this error receipt is from a previous upgrade
+ abortTransactionUnless(errorReceipt.sequence >= channel.upgradeSequence)
+ }
+ // fastforward channel sequence to higher sequence so that we can start
+ // new handshake on a fresh sequence
+ channel.upgradeSequence = errorReceipt.sequence
+ provableStore.set(channelPath(portIdentifier, channelIdentifier), channel)
+
+ // get underlying connection for proof verification
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ // verify that the provided error receipt is written to the upgradeError path with the counterparty sequence
+ abortTransactionUnless(
+ verifyChannelUpgradeError(
+ connection,
+ proofHeight,
+ proofUpgradeError,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ errorReceipt
+ )
+ )
+ }
+
+ // cancel upgrade and write error receipt
+ restoreChannel(portIdentifier, channelIdentifier)
+}
+```
+
+### Timeout Upgrade Process
+
+It is possible for the channel upgrade process to stall indefinitely while trying to flush the existing packets. To protect against this, each chain sets a timeout when it moves into `FLUSHING`. If the counterparty has not completed flushing within the expected time window, then the relayer can submit a timeout message to restore the channel to `OPEN` with the original parameters. It will also write an error receipt so that the counterparty which has not moved to `FLUSHCOMPLETE` can also restore channel to `OPEN` with the original parameters.
+
+```typescript
+function timeoutChannelUpgrade(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannel: ChannelEnd,
+ proofChannel: CommitmentProof,
+ proofHeight: Height,
+) {
+ // current channel must have an upgrade that is FLUSHING or FLUSHCOMPLETE
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(upgrade !== null)
+ channel = provableStore.get(channelPath(portIdentifier, channelIdentifier))
+ abortTransactionUnless(channel.state === FLUSHING || channel.state === FLUSHCOMPLETE)
+
+ upgradeTimeout = upgrade.timeout
+
+ // proof must be from a height after timeout has elapsed.
+ // Either timeoutHeight or timeoutTimestamp must be defined.
+ // if timeoutHeight is defined and proof is from before
+ // timeout height then abort transaction
+ abortTransactionUnless(
+ upgradeTimeout.timeoutHeight.IsZero() ||
+ proofHeight >= upgradeTimeout.timeoutHeight
+ )
+ // if timeoutTimestamp is defined then the consensus time
+ // from proof height must be greater than timeout timestamp
+ connection = provableStore.get(connectionPath(channel.connectionHops[0]))
+ abortTransactionUnless(
+ upgradeTimeout.timeoutTimestamp.IsZero() ||
+ getTimestampAtHeight(connection, proofHeight) >= upgradeTimeout.timestamp
+ )
+
+ // counterparty channel must be proved to not have completed flushing after timeout has passed
+ abortTransactionUnless(counterpartyChannel.state !== FLUSHCOMPLETE)
+ // if counterparty channel state is OPEN, we should abort the tx
+ // only if the counterparty has successfully completed upgrade
+ if (counterpartyChannel.state == OPEN) {
+ // get upgrade since counterparty should have upgraded to these parameters
+ upgrade = provableStore.get(channelUpgradePath(portIdentifier, channelIdentifier))
+
+ // get counterparty hops of the proposed connection
+ proposedConnection = provableStore.get(connectionPath(upgrade.fields.connectionHops))
+ counterpartyHops = [proposedConnection.counterpartyConnectionIdentifier]
+
+ // check that the channel did not upgrade successfully
+ if ((upgrade.fields.version == counterpartyChannel.version) &&
+ (upgrade.fields.order == counterpartyChannel.order) &&
+ (counterpartyHops == counterpartyChannel.connectionHops)) {
+ // counterparty has already successfully upgraded so we cannot timeout
+ abortTransactionUnless(false)
+ }
+ }
+ abortTransactionUnless(counterpartyChannel.upgradeSequence >= channel.upgradeSequence)
+ abortTransactionUnless(
+ verifyChannelState(
+ connection,
+ proofHeight,
+ proofChannel,
+ channel.counterpartyPortIdentifier,
+ channel.counterpartyChannelIdentifier,
+ counterpartyChannel
+ )
+ )
+
+ // we must restore the channel since the timeout verification has passed
+ // error receipt is written for this sequence, counterparty can call cancelUpgradeHandshake
+ restoreChannel(portIdentifier, channelIdentifier)
+}
+```
+
+Both parties must not complete the upgrade handshake and move to `FLUSHCOMPLETE` if the counterparty upgrade timeout has already passed. This will prevent the channel ends from reaching incompatible states.
+
+### Considerations
+
+Note that a channel upgrade handshake may never complete successfully if the in-flight packets cannot successfully be cleared. This can happen if the timeout value of a packet is too large, or an acknowledgement never arrives, or if there is a bug that makes acknowledging or timing out a packet impossible. In these cases, some out-of-protocol mechanism (e.g. governance) must step in to clear the packets "manually" perhaps by forcefully clearing the packet commitments before restarting the upgrade handshake.
+
+### Migrations
+
+A chain may have to update its internal state to be consistent with the new upgraded channel. In this case, a migration handler should be a part of the chain binary before the upgrade process so that the chain can properly migrate its state once the upgrade is successful. If a migration handler is necessary for a given upgrade but is not available, then the executing chain must reject the upgrade so as not to enter into an invalid state. This state migration will not be verified by the counterparty since it will just assume that if the channel is upgraded to a particular channel version, then the auxiliary state on the counterparty will also be updated to match the specification for the given channel version. The migration must only run once the upgrade has successfully completed and the new channel is `OPEN` (ie. on `ChanUpgradeConfirm` or `ChanUpgradeOpen`).
+
+## Example Implementations
+
+- Implementation of channel upgrade in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+
+## History
+
+Feb 1, 2024 - Spec as implemented in ibc-go
+
+Jul 24, 2024 - [Add upgrade compatibility check in `chanUpgradeConfirm`](https://github.com/cosmos/ibc/pull/1127)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png
new file mode 100644
index 00000000..9d0ce4dd
Binary files /dev/null and b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-state-machine.png differ
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-upgrade-flow.png b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-upgrade-flow.png
new file mode 100644
index 00000000..fdfd9ccd
Binary files /dev/null and b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/channel-upgrade-flow.png differ
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/dataflow.png b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/dataflow.png
new file mode 100644
index 00000000..082a4461
Binary files /dev/null and b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/dataflow.png differ
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png
new file mode 100644
index 00000000..958284fc
Binary files /dev/null and b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-state-machine.png differ
diff --git a/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png
new file mode 100644
index 00000000..4c6ac794
Binary files /dev/null and b/ibc/next/spec/core/ics-004-channel-and-packet-semantics/packet-transit.png differ
diff --git a/ibc/next/spec/core/ics-005-port-allocation/README.mdx b/ibc/next/spec/core/ics-005-port-allocation/README.mdx
new file mode 100644
index 00000000..b0f59729
--- /dev/null
+++ b/ibc/next/spec/core/ics-005-port-allocation/README.mdx
@@ -0,0 +1,249 @@
+---
+ics: 5
+title: Port Allocation
+stage: draft
+category: IBC/TAO
+kind: interface
+requires: [24]
+required_by: [4]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-06-20
+modified: 2019-08-25
+---
+
+## Synopsis
+
+This standard specifies the port allocation system by which modules can bind to uniquely named ports allocated by the IBC handler.
+Ports can then be used to open channels and can be transferred or later released by the module which originally bound to them.
+
+### Motivation
+
+The interblockchain communication protocol is designed to facilitate module-to-module traffic, where modules are independent, possibly mutually distrusted, self-contained
+elements of code executing on sovereign ledgers. In order to provide the desired end-to-end semantics, the IBC handler must permission channels to particular modules.
+This specification defines the *port allocation and ownership* system which realises that model.
+
+Conventions may emerge as to what kind of module logic is bound to a particular port name, such as "bank" for fungible token handling or "staking" for interchain collateralisation.
+This is analogous to port 80's common use for HTTP servers — the protocol cannot enforce that particular module logic is actually bound to conventional ports, so
+users must check that themselves. Ephemeral ports with pseudorandom identifiers may be created for temporary protocol handling.
+
+Modules may bind to multiple ports and connect to multiple ports bound to by another module on a separate machine. Any number of (uniquely identified) channels can utilise a single
+port simultaneously. Channels are end-to-end between two ports, each of which must have been previously bound to by a module, which will then control that end of the channel.
+
+Optionally, the host state machine can elect to expose port binding only to a specially-permissioned module manager,
+by generating a capability key specifically for the ability to bind ports. The module manager
+can then control which ports modules can bind to with a custom rule-set, and transfer ports to modules only when it
+has validated the port name & module. This role can be played by the routing module (see [ICS 26](../ics-026-routing-module)).
+
+### Definitions
+
+`Identifier`, `get`, `set`, and `delete` are defined as in [ICS 24](../ics-024-host-requirements).
+
+A *port* is a particular kind of identifier which is used to permission channel opening and usage to modules.
+
+A *module* is a sub-component of the host state machine independent of the IBC handler. Examples include Ethereum smart contracts and Cosmos SDK & Substrate modules.
+The IBC specification makes no assumptions of module functionality other than the ability of the host state machine to use object-capability or source authentication to permission ports to modules.
+
+### Desired Properties
+
+- Once a module has bound to a port, no other modules can use that port until the module releases it
+- A module can, on its option, release a port or transfer it to another module
+- A single module can bind to multiple ports at once
+- Ports are allocated first-come first-serve, and "reserved" ports for known modules can be bound when the chain is first started
+
+As a helpful comparison, the following analogies to TCP are roughly accurate:
+
+| IBC Concept | TCP/IP Concept | Differences |
+| ----------------------- | ------------------------- | --------------------------------------------------------------------- |
+| IBC | TCP | Many, see the architecture documents describing IBC |
+| Port (e.g. "bank") | Port (e.g. 80) | No low-number reserved ports, ports are strings |
+| Module (e.g. "bank") | Application (e.g. Nginx) | Application-specific |
+| Client | - | No direct analogy, a bit like L2 routing and a bit like TLS |
+| Connection | - | No direct analogy, folded into connections in TCP |
+| Channel | Connection | Any number of channels can be opened to or from a port simultaneously |
+
+## Technical Specification
+
+### Data Structures
+
+The host state machine MUST support either object-capability reference or source authentication for modules.
+
+In the former object-capability case, the IBC handler must have the ability to generate *object-capabilities*, unique, opaque references
+which can be passed to a module and will not be duplicable by other modules. Two examples are store keys as used in the Cosmos SDK ([reference](https://github.com/cosmos/cosmos-sdk/blob/97eac176a5d533838333f7212cbbd79beb0754bc/store/types/store.go#L275))
+and object references as used in Agoric's Javascript runtime ([reference](https://github.com/Agoric/SwingSet)).
+
+```typescript
+type CapabilityKey object
+```
+
+`newCapability` must take a name and generate a unique capability key, such that the name is locally mapped to the capability key and can be used with `getCapability` later.
+
+```typescript
+function newCapability(name: string): CapabilityKey {
+ // provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
+}
+```
+
+`authenticateCapability` must take a name & a capability and check whether the name is locally mapped to the provided capability. The name can be untrusted user input.
+
+```typescript
+function authenticateCapability(name: string, capability: CapabilityKey): bool {
+ // provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
+}
+```
+
+`claimCapability` must take a name & a capability (provided by another module) and locally map the name to the capability, "claiming" it for future usage.
+
+```typescript
+function claimCapability(name: string, capability: CapabilityKey) {
+ // provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
+}
+```
+
+`getCapability` must allow a module to lookup a capability which it has previously created or claimed by name.
+
+```typescript
+function getCapability(name: string): CapabilityKey {
+ // provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
+}
+```
+
+`releaseCapability` must allow a module to release a capability which it owns.
+
+```typescript
+function releaseCapability(capability: CapabilityKey) {
+ // provided by host state machine, e.g. ADR 3 / ScopedCapabilityKeeper in Cosmos SDK
+}
+```
+
+In the latter source authentication case, the IBC handler must have the ability to securely read the *source identifier* of the calling module,
+a unique string for each module in the host state machine, which cannot be altered by the module or faked by another module.
+An example is smart contract addresses as used by Ethereum ([reference](https://ethereum.github.io/yellowpaper/paper.pdf)).
+
+```typescript
+type SourceIdentifier string
+```
+
+```typescript
+function callingModuleIdentifier(): SourceIdentifier {
+ // provided by host state machine, e.g. contract address in Ethereum
+}
+```
+
+`newCapability`, `authenticateCapability`, `claimCapability`, `getCapability`, and `releaseCapability` are then implemented as follows:
+
+```typescript
+function newCapability(name: string): CapabilityKey {
+ return callingModuleIdentifier()
+}
+```
+
+```typescript
+function authenticateCapability(name: string, capability: CapabilityKey) {
+ return callingModuleIdentifier() === name
+}
+```
+
+```typescript
+function claimCapability(name: string, capability: CapabilityKey) {
+ // no-op
+}
+```
+
+```typescript
+function getCapability(name: string): CapabilityKey {
+ // not actually used
+ return nil
+}
+```
+
+```typescript
+function releaseCapability(capability: CapabilityKey) {
+ // no-op
+}
+```
+
+#### Store paths
+
+`portPath` takes an `Identifier` and returns the store path under which the object-capability reference or owner module identifier associated with a port should be stored.
+
+```typescript
+function portPath(id: Identifier): Path {
+ return "ports/{id}"
+}
+```
+
+### Sub-protocols
+
+#### Identifier validation
+
+Owner module identifier for ports are stored under a unique `Identifier` prefix.
+The validation function `validatePortIdentifier` MAY be provided.
+
+```typescript
+type validatePortIdentifier = (id: Identifier) => boolean
+```
+
+If not provided, the default `validatePortIdentifier` function will always return `true`.
+
+#### Binding to a port
+
+The IBC handler MUST implement `bindPort`. `bindPort` binds to an unallocated port, failing if the port has already been allocated.
+
+If the host state machine does not implement a special module manager to control port allocation, `bindPort` SHOULD be available to all modules. If it does, `bindPort` SHOULD only be callable by the module manager.
+
+```typescript
+function bindPort(id: Identifier): CapabilityKey {
+ abortTransactionUnless(validatePortIdentifier(id))
+ abortTransactionUnless(getCapability(portPath(id)) === null)
+ capability = newCapability(portPath(id))
+ return capability
+}
+```
+
+#### Transferring ownership of a port
+
+If the host state machine supports object-capabilities, no additional protocol is necessary, since the port reference is a bearer capability.
+
+#### Releasing a port
+
+The IBC handler MUST implement the `releasePort` function, which allows a module to release a port such that other modules may then bind to it.
+
+`releasePort` SHOULD be available to all modules.
+
+> Warning: releasing a port will allow other modules to bind to that port and possibly intercept incoming channel opening handshakes. Modules should release ports only when doing so is safe.
+
+```typescript
+function releasePort(id: Identifier, capability: CapabilityKey) {
+ abortTransactionUnless(authenticateCapability(portPath(id), capability))
+ releaseCapability(capability)
+}
+```
+
+### Properties & Invariants
+
+- By default, port identifiers are first-come-first-serve: once a module has bound to a port, only that module can utilise the port until the module transfers or releases it. A module manager can implement custom logic which overrides this.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Port binding is not a wire protocol, so interfaces can change independently on separate chains as long as the ownership semantics are unaffected.
+
+## Example Implementations
+
+- Implementation of ICS 05 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 05 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Jun 29, 2019 - Initial draft
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-023-vector-commitments/README.mdx b/ibc/next/spec/core/ics-023-vector-commitments/README.mdx
new file mode 100644
index 00000000..5d86dfd1
--- /dev/null
+++ b/ibc/next/spec/core/ics-023-vector-commitments/README.mdx
@@ -0,0 +1,304 @@
+---
+ics: 23
+title: Vector Commitments
+stage: draft
+category: IBC/TAO
+kind: interface
+required_by: [2, 24]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-04-16
+modified: 2019-08-25
+---
+
+## Synopsis
+
+A *vector commitment* is a construction that produces a constant-size, binding commitment to an indexed vector of elements and short membership and/or non-membership proofs for any indices & elements in the vector.
+This specification enumerates the functions and properties required of commitment constructions used in the IBC protocol. In particular, commitments utilised in IBC are required to be *positionally binding*: they must be able to prove existence or
+nonexistence of values at specific positions (indices).
+
+### Motivation
+
+In order to provide a guarantee of a particular state transition having occurred on one chain which can be verified on another chain, IBC requires an efficient cryptographic construction to prove inclusion or non-inclusion of particular values at particular paths in state.
+
+### Definitions
+
+The *manager* of a vector commitment is the actor with the ability and responsibility to add or remove items from the commitment. Generally this will be the state machine of a blockchain.
+
+The *prover* is the actor responsible for generating proofs of inclusion or non-inclusion of particular elements. Generally this will be a relayer (see [ICS 18](../../relayer/ics-018-relayer-algorithms)).
+
+The *verifier* is the actor who checks proofs in order to verify that the manager of the commitment did or did not add a particular element. Generally this will be an IBC handler (module implementing IBC) running on another chain.
+
+Commitments are instantiated with particular *path* and *value* types, which are assumed to be arbitrary serialisable data.
+
+A *negligible function* is a function that grows more slowly than the reciprocal of every positive polynomial, as defined [here](https://en.wikipedia.org/wiki/Negligible_function).
+
+### Desired Properties
+
+This document only defines desired properties, not a concrete implementation — see "Properties" below.
+
+## Technical Specification
+
+Below we define a behaviour and an overview of datatypes. For data type definition look at [cosmos/ics23](https://github.com/cosmos/ics23/blob/master/proto/cosmos/ics23/v1/proofs.proto) repository.
+
+### Datatypes
+
+A commitment construction MUST specify the following datatypes, which are otherwise opaque (need not be introspected) but MUST be serialisable:
+
+#### Commitment State
+
+A `CommitmentState` is the full state of the commitment, which will be stored by the manager.
+
+```typescript
+type CommitmentState = object
+```
+
+#### Commitment Root
+
+A `CommitmentRoot` commits to a particular commitment state and should be constant-size.
+
+In certain commitment constructions with constant-size states, `CommitmentState` and `CommitmentRoot` may be the same type.
+
+```typescript
+type CommitmentRoot = object
+```
+
+#### Commitment Path
+
+A `CommitmentPath` is the path used to verify commitment proofs, which can be an arbitrary structured object (defined by a commitment type). It must be computed by `applyPrefix` (defined below).
+
+```typescript
+type CommitmentPath = object
+```
+
+#### Prefix
+
+A `CommitmentPrefix` defines a store prefix of the commitment proof. It is applied to the path before the path is passed to the proof verification functions.
+
+```typescript
+type CommitmentPrefix = object
+```
+
+The function `applyPrefix` constructs a new commitment path from the arguments. It interprets the path argument in the context of the prefix argument.
+
+For two `(prefix, path)` tuples, `applyPrefix(prefix, path)` MUST return the same key only if the tuple elements are equal.
+
+`applyPrefix` MUST be implemented per `Path`, as `Path` can have different concrete structures. `applyPrefix` MAY accept multiple `CommitmentPrefix` types.
+
+The `CommitmentPath` returned by `applyPrefix` does not need to be serialisable (e.g. it might be a list of tree node identifiers), but it does need an equality comparison.
+
+```typescript
+type applyPrefix = (prefix: CommitmentPrefix, path: Path) => CommitmentPath
+```
+
+The function `removePrefix` is the inverse operation of `applyPrefix`, i.e. it returns the bytestring key without the store prefix.
+
+```typescript
+type removePrefix = (prefix: CommitmentPrefix, path: CommitmentPath) => Path
+```
+
+#### Proof
+
+A `CommitmentProof` demonstrates membership or non-membership for an element or set of elements, verifiable in conjunction with a known commitment root. Proofs should be succinct.
+
+```typescript
+type CommitmentProof = object
+```
+
+### Required functions
+
+A commitment construction MUST provide the following functions, defined over paths as serialisable objects and values as byte arrays:
+
+```typescript
+type Path = string
+
+type Value = []byte
+```
+
+#### Initialisation
+
+The `generate` function initialises the state of the commitment from an initial (possibly empty) map of paths to values.
+
+```typescript
+type generate = (initial: Map) => CommitmentState
+```
+
+#### Root calculation
+
+The `calculateRoot` function calculates a constant-size commitment to the commitment state which can be used to verify proofs.
+
+```typescript
+type calculateRoot = (state: CommitmentState) => CommitmentRoot
+```
+
+#### Adding & removing elements
+
+The `set` function sets a path to a value in the commitment.
+
+```typescript
+type set = (state: CommitmentState, path: Path, value: Value) => CommitmentState
+```
+
+The `remove` function removes a path and associated value from a commitment.
+
+```typescript
+type remove = (state: CommitmentState, path: Path) => CommitmentState
+```
+
+#### Proof generation
+
+The `createMembershipProof` function generates a proof that a particular commitment path has been set to a particular value in a commitment.
+
+```typescript
+type createMembershipProof = (state: CommitmentState, path: CommitmentPath, value: Value) => CommitmentProof
+```
+
+The `createNonMembershipProof` function generates a proof that a commitment path has not been set to any value in a commitment.
+
+```typescript
+type createNonMembershipProof = (state: CommitmentState, path: CommitmentPath) => CommitmentProof
+```
+
+#### Proof verification
+
+The `verifyMembership` function verifies a proof that a path has been set to a particular value in a commitment.
+
+```typescript
+type verifyMembership = (root: CommitmentRoot, proof: CommitmentProof, path: CommitmentPath, value: Value) => boolean
+```
+
+The `verifyNonMembership` function verifies a proof that a path has not been set to any value in a commitment.
+
+```typescript
+type verifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, path: CommitmentPath) => boolean
+```
+
+### Optional functions
+
+A commitment construction MAY provide the following functions:
+
+The `batchVerifyMembership` function verifies a proof that many paths have been set to specific values in a commitment.
+
+```typescript
+type batchVerifyMembership = (root: CommitmentRoot, proof: CommitmentProof, items: Map) => boolean
+```
+
+The `batchVerifyNonMembership` function verifies a proof that many paths have not been set to any value in a commitment.
+
+```typescript
+type batchVerifyNonMembership = (root: CommitmentRoot, proof: CommitmentProof, paths: Set) => boolean
+```
+
+If defined, these functions MUST produce the same result as the conjunctive union of `verifyMembership` and `verifyNonMembership` respectively (efficiency may vary):
+
+```typescript
+batchVerifyMembership(root, proof, items) ===
+ all(items.map((item) => verifyMembership(root, proof, item.path, item.value)))
+```
+
+```typescript
+batchVerifyNonMembership(root, proof, items) ===
+ all(items.map((item) => verifyNonMembership(root, proof, item.path)))
+```
+
+If batch verification is possible and more efficient than individual verification of one proof per element, a commitment construction SHOULD define batch verification functions.
+
+### Properties & Invariants
+
+Commitments MUST be *complete*, *sound*, and *position binding*. These properties are defined with respect to a security parameter `k`, which MUST be agreed upon by the manager, prover, and verifier (and often will be constant for the commitment algorithm).
+
+#### Completeness
+
+Commitment proofs MUST be *complete*: path => value mappings which have been added to the commitment can always be proved to have been included, and paths which have not been included can always be proved to have been excluded, except with probability negligible in `k`.
+
+For any prefix `prefix` and any path `path` last set to a value `value` in the commitment `acc`,
+
+```typescript
+root = getRoot(acc)
+proof = createMembershipProof(acc, applyPrefix(prefix, path), value)
+```
+
+```typescript
+Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === false) negligible in k
+```
+
+For any prefix `prefix` and any path `path` not set in the commitment `acc`, for all values of `proof` and all values of `value`,
+
+```typescript
+root = getRoot(acc)
+proof = createNonMembershipProof(acc, applyPrefix(prefix, path))
+```
+
+```typescript
+Probability(verifyNonMembership(root, proof, applyPrefix(prefix, path)) === false) negligible in k
+```
+
+#### Soundness
+
+Commitment proofs MUST be *sound*: path => value mappings which have not been added to the commitment cannot be proved to have been included, or paths which have been added to the commitment excluded, except with probability negligible in a configurable security parameter `k`.
+
+For any prefix `prefix` and any path `path` last set to a value `value` in the commitment `acc`, for all values of `proof`,
+
+```typescript
+Probability(verifyNonMembership(root, proof, applyPrefix(prefix, path)) === true) negligible in k
+```
+
+For any prefix `prefix` and any path `path` not set in the commitment `acc`, for all values of `proof` and all values of `value`,
+
+```typescript
+Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === true) negligible in k
+```
+
+To ensure the commitment proofs are *sound*, the commitment must be lexicographically ordered to ensure that non-existence proofs of the key `b` may be proven by showing the existence of key `a` and key `c` in addition to proving that these two keys are neighbors in the commitment.
+
+#### Position binding
+
+Commitment proofs MUST be *position binding*: a given commitment path can only map to one value, and a commitment proof cannot prove that the same path opens to a different value except with probability negligible in k.
+
+For any prefix `prefix` and any path `path` set in the commitment `acc`, there is one `value` for which:
+
+```typescript
+root = getRoot(acc)
+proof = createMembershipProof(acc, applyPrefix(prefix, path), value)
+```
+
+```typescript
+Probability(verifyMembership(root, proof, applyPrefix(prefix, path), value) === false) negligible in k
+```
+
+For all other values `otherValue` where `value !== otherValue`, for all values of `proof`,
+
+```typescript
+Probability(verifyMembership(root, proof, applyPrefix(prefix, path), otherValue) === true) negligible in k
+```
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Commitment algorithms are expected to be fixed. New algorithms can be introduced by versioning connections and channels.
+
+## Example Implementations
+
+- Implementations of ICS 23 in Go and Rust can be found in [cosmos/ics23 repository](https://github.com/cosmos/ics23).
+
+## History
+
+Security definitions are mostly sourced from these papers (and simplified somewhat):
+
+- [Vector Commitments and their Applications](https://eprint.iacr.org/2011/495.pdf)
+- [Commitments with Applications to Anonymity-Preserving Revocation](https://eprint.iacr.org/2017/043.pdf)
+- [Batching Techniques for Commitments with Applications to IOPs and Stateless Blockchains](https://eprint.iacr.org/2018/1188.pdf)
+
+Thanks to Dev Ojha for extensive comments on this specification.
+
+Apr 25, 2019 - Draft submitted
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-024-host-requirements/README.mdx b/ibc/next/spec/core/ics-024-host-requirements/README.mdx
new file mode 100644
index 00000000..80e56ee9
--- /dev/null
+++ b/ibc/next/spec/core/ics-024-host-requirements/README.mdx
@@ -0,0 +1,414 @@
+---
+ics: 24
+title: Host State Machine Requirements
+stage: draft
+category: IBC/TAO
+kind: interface
+requires: [23]
+required_by: [2, 3, 4, 5, 18]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-04-16
+modified: 2022-09-14
+---
+
+## Synopsis
+
+This specification defines the minimal set of interfaces which must be provided and properties which must be fulfilled by a state machine hosting an implementation of the interblockchain communication protocol.
+
+### Motivation
+
+IBC is designed to be a common standard which will be hosted by a variety of blockchains & state machines and must clearly define the requirements of the host.
+
+### Definitions
+
+### Desired Properties
+
+IBC should require as simple an interface from the underlying state machine as possible to maximise the ease of correct implementation.
+
+## Technical Specification
+
+### Module system
+
+The host state machine must support a module system, whereby self-contained, potentially mutually distrusted packages of code can safely execute on the same ledger, control how and when they allow other modules to communicate with them, and be identified and manipulated by a "master module" or execution environment.
+
+The IBC/TAO specifications define the implementations of two modules: the core "IBC handler" module and the "IBC relayer" module. IBC/APP specifications further define other modules for particular packet handling application logic. IBC requires that the "master module" or execution environment can be used to grant other modules on the host state machine access to the IBC handler module and/or the IBC routing module, but otherwise does not impose requirements on the functionality or communication abilities of any other modules which may be co-located on the state machine.
+
+### Paths, identifiers, separators
+
+An `Identifier` is a bytestring used as a key for an object stored in state, such as a connection, channel, or light client.
+
+Identifiers MUST be non-empty (of positive integer length).
+
+Identifiers MUST consist of characters in one of the following categories only:
+
+- Alphanumeric
+- `.`, `_`, `+`, `-`, `#`
+- `[`, `]`, `<`, `>`
+
+A `Path` is a bytestring used as the key for an object stored in state. Paths MUST contain only identifiers, constant strings, and the separator `"/"`.
+
+Identifiers are not intended to be valuable resources — to prevent name squatting, minimum length requirements or pseudorandom generation MAY be implemented, but particular restrictions are not imposed by this specification.
+
+The separator `"/"` is used to separate and concatenate two identifiers or an identifier and a constant bytestring. Identifiers MUST NOT contain the `"/"` character, which prevents ambiguity.
+
+Variable interpolation, denoted by curly braces, is used throughout this specification as shorthand to define path formats, e.g. `client/{clientIdentifier}/consensusState`.
+
+All identifiers, and all strings listed in this specification, must be encoded as ASCII unless otherwise specified.
+
+By default, identifiers have the following minimum and maximum lengths in characters:
+
+| Port identifier | Client identifier | Connection identifier | Channel identifier |
+| --------------- | ----------------- | --------------------- | ------------------ |
+| 2 - 128 | 9 - 64 | 10 - 64 | 8 - 64 |
+
+### Key/value Store
+
+The host state machine MUST provide a key/value store interface
+with three functions that behave in the standard way:
+
+```typescript
+type get = (path: Path) => Value | void
+```
+
+```typescript
+type set = (path: Path, value: Value) => void
+```
+
+```typescript
+type delete = (path: Path) => void
+```
+
+`Path` is as defined above. `Value` is an arbitrary bytestring encoding of a particular data structure. Encoding details are left to separate ICSs.
+
+These functions MUST be permissioned to the IBC handler module (the implementation of which is described in separate standards) only, so only the IBC handler module can `set` or `delete` the paths that can be read by `get`. This can possibly be implemented as a sub-store (prefixed key-space) of a larger key/value store used by the entire state machine.
+
+Host state machines MUST provide two instances of this interface -
+a `provableStore` for storage read by (i.e. proven to) other chains,
+and a `privateStore` for storage local to the host, upon which `get`
+, `set`, and `delete` can be called, e.g. `provableStore.set('some/path', 'value')`.
+
+The `provableStore`:
+
+- MUST write to a key/value store whose data can be externally proved with a vector commitment as defined in [ICS 23](../ics-023-vector-commitments).
+- MUST use canonical data structure encodings provided in these specifications as proto3 files
+
+The `privateStore`:
+
+- MAY support external proofs, but is not required to - the IBC handler will never write data to it which needs to be proved.
+- MAY use canonical proto3 data structures, but is not required to - it can use
+ whatever format is preferred by the application environment.
+
+> Note: any key/value store interface which provides these methods & properties is sufficient for IBC. Host state machines may implement "proxy stores" with path & value mappings which do not directly match the path & value pairs set and retrieved through the store interface — paths could be grouped into buckets & values stored in pages which could be proved in a single commitment, path-spaces could be remapped non-contiguously in some bijective manner, etc — as long as `get`, `set`, and `delete` behave as expected and other machines can verify commitment proofs of path & value pairs (or their absence) in the provable store. If applicable, the store must expose this mapping externally so that clients (including relayers) can determine the store layout & how to construct proofs. Clients of a machine using such a proxy store must also understand the mapping, so it will require either a new client type or a parameterised client.
+>
+> Note: this interface does not necessitate any particular storage backend or backend data layout. State machines may elect to use a storage backend configured in accordance with their needs, as long as the store on top fulfils the specified interface and provides commitment proofs.
+
+### Path-space
+
+At present, IBC/TAO recommends the following path prefixes for the `provableStore` and `privateStore`.
+
+Future paths may be used in future versions of the protocol, so the entire key-space in the provable store MUST be reserved for the IBC handler.
+
+Keys used in the provable store MAY safely vary on a per-client-type basis as long as there exists a bipartite mapping between the key formats
+defined herein and the ones actually used in the machine's implementation.
+
+Parts of the private store MAY safely be used for other purposes as long as the IBC handler has exclusive access to the specific keys required.
+Keys used in the private store MAY safely vary as long as there exists a bipartite mapping between the key formats defined herein and the ones
+actually used in the private store implementation.
+
+Note that the client-related paths listed below reflect the Tendermint client as defined in [ICS 7](../../client/ics-007-tendermint-client) and may vary for other client types.
+
+| Store | Path format | Value type | Defined in |
+| -------------- | ------------------------------------------------------------------------------ | ----------------- | ---------------------- |
+| provableStore | "clients/{identifier}/clientState" | ClientState | [ICS 2](../ics-002-client-semantics) |
+| provableStore | "clients/{identifier}/consensusStates/{height}" | ConsensusState | [ICS 7](../../client/ics-007-tendermint-client) |
+| privateStore | "clients/{identifier}/connections | []Identifier | [ICS 3](../ics-003-connection-semantics) |
+| provableStore | "connections/{identifier}" | ConnectionEnd | [ICS 3](../ics-003-connection-semantics) |
+| privateStore | "ports/{identifier}" | CapabilityKey | [ICS 5](../ics-005-port-allocation) |
+| provableStore | "channelEnds/ports/{identifier}/channels/{identifier}" | ChannelEnd | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "nextSequenceSend/ports/{identifier}/channels/{identifier}" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "nextSequenceRecv/ports/{identifier}/channels/{identifier}" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "nextSequenceAck/ports/{identifier}/channels/{identifier}" | uint64 | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "commitments/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "receipts/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) |
+| provableStore | "acks/ports/{identifier}/channels/{identifier}/sequences/{sequence}" | bytes | [ICS 4](../ics-004-channel-and-packet-semantics) |
+
+### Module layout
+
+Represented spatially, the layout of modules & their included specifications on a host state machine looks like so (Aardvark, Betazoid, and Cephalopod are arbitrary modules):
+
+```shell
++----------------------------------------------------------------------------------+
+| |
+| Host State Machine |
+| |
+| +-------------------+ +--------------------+ +----------------------+ |
+| | Module Aardvark | <--> | IBC Routing Module | | IBC Handler Module | |
+| +-------------------+ | | | | |
+| | Implements ICS 26. | | Implements ICS 2, 3, | |
+| | | | 4, 5 internally. | |
+| +-------------------+ | | | | |
+| | Module Betazoid | <--> | | --> | Exposes interface | |
+| +-------------------+ | | | defined in ICS 25. | |
+| | | | | |
+| +-------------------+ | | | | |
+| | Module Cephalopod | <--> | | | | |
+| +-------------------+ +--------------------+ +----------------------+ |
+| |
++----------------------------------------------------------------------------------+
+```
+
+### Consensus state introspection
+
+Host state machines MUST provide the ability to introspect their current height, with `getCurrentHeight`:
+
+```typescript
+type getCurrentHeight = () => Height
+```
+
+Host state machines MUST define a unique `ConsensusState` type fulfilling the requirements of [ICS 2](../ics-002-client-semantics), with a canonical binary serialisation.
+
+Host state machines MUST provide the ability to introspect their own consensus state, with `getConsensusState`:
+
+```typescript
+type getConsensusState = (height: Height, proof?: bytes) => ConsensusState
+```
+
+`getConsensusState` MUST return the consensus state for at least some number `n` of contiguous recent heights, where `n` is constant for the host state machine. Heights older than `n` MAY be safely pruned (causing future calls to fail for those heights).
+
+We provide an optional proof data which comes from the `MsgConnectionOpenAck` or `MsgConnectionOpenTry` for host state machines which are unable to introspect their own `ConsensusState` and must rely on off-chain data.
+
+In this case host state machines MUST maintain a map of `n` block numbers to header hashes where the proof would contain full header which can be hashed and compared with the on-chain record.
+
+Host state machines MUST provide the ability to introspect this stored recent consensus state count `n`, with `getStoredRecentConsensusStateCount`:
+
+```typescript
+type getStoredRecentConsensusStateCount = () => Height
+```
+
+### Client state validation
+
+Host state machines MUST define a unique `ClientState` type fulfilling the requirements of [ICS 2](../ics-002-client-semantics).
+
+Host state machines MUST provide the ability to construct a `ClientState` representation of their own state for the purposes of client state validation, with `getHostClientState`:
+
+```typescript
+type getHostClientState = (height: Height) => ClientState
+```
+
+Host state machines MUST provide the ability to validate the `ClientState` of a light client running on a counterparty chain, with `validateSelfClient`:
+
+```typescript
+type validateSelfClient = (counterpartyClientState: ClientState) => boolean
+```
+
+`validateSelfClient` validates the client parameters for a client of the host chain. For example, below is the implementation for Tendermint hosts, using `ClientState` as defined in [ICS 7](../../client/ics-007-tendermint-client/):
+
+```typescript
+function validateSelfClient(counterpartyClientState: ClientState) {
+ hostClientState = getHostClientState()
+
+ // assert that the counterparty client is not frozen
+ if counterpartyClientState.frozenHeight !== null {
+ return false
+ }
+
+ // assert that the chain ids are the same
+ if counterpartyClientState.chainID !== hostClientState.chainID {
+ return false
+ }
+
+ // assert that the counterparty client is in the same revision as the host chain
+ counterpartyRevisionNumber = parseRevisionNumber(counterpartyClientState.chainID)
+ if counterpartyRevisionNumber !== hostClientState.latestHeight.revisionNumber {
+ return false
+ }
+
+ // assert that the counterparty client has a height less than the host height
+ if counterpartyClientState.latestHeight >= hostClientState.latestHeight {
+ return false
+ }
+
+ // assert that the counterparty client has the same ProofSpec as the host
+ if counterpartyClientState.proofSpecs !== hostClientState.proofSpecs {
+ return false
+ }
+
+ // assert that the trustLevel is within the allowed range. 1/3 is the minimum amount
+ // of trust needed which does not break the security model.
+ if counterpartyClientState.trustLevel < 1/3 || counterpartyClientState.trustLevel > 1 {
+ return false
+ }
+
+ // assert that the unbonding periods are the same
+ if counterpartyClientState.unbondingPeriod != hostClientState.unbondingPeriod {
+ return false
+ }
+
+ // assert that the unbonding period is greater than or equal to the trusting period
+ if counterpartyClientState.unbondingPeriod < counterpartyClientState.trustingPeriod {
+ return false
+ }
+
+ // assert that the upgrade paths are the same
+ hostUpgradePath = applyPrefix(hostClientState.upgradeCommitmentPrefix, hostClientState.upgradeKey)
+ counterpartyUpgradePath = applyPrefix(counterpartyClientState.upgradeCommitmentPrefix, counterpartyClientState.upgradeKey)
+ if counterpartyUpgradePath !== hostUpgradePath {
+ return false
+ }
+
+ return true
+}
+```
+
+### Commitment path introspection
+
+Host chains MUST provide the ability to inspect their commitment path, with `getCommitmentPrefix`:
+
+```typescript
+type getCommitmentPrefix = () => CommitmentPrefix
+```
+
+The result `CommitmentPrefix` is the prefix used by the host state machine's key-value store.
+With the `CommitmentRoot root` and `CommitmentState state` of the host state machine, the following property MUST be preserved:
+
+```typescript
+if provableStore.get(path) === value {
+ prefixedPath = applyPrefix(getCommitmentPrefix(), path)
+ if value !== nil {
+ proof = createMembershipProof(state, prefixedPath, value)
+ assert(verifyMembership(root, proof, prefixedPath, value))
+ } else {
+ proof = createNonMembershipProof(state, prefixedPath)
+ assert(verifyNonMembership(root, proof, prefixedPath))
+ }
+}
+```
+
+For a host state machine, the return value of `getCommitmentPrefix` MUST be constant.
+
+### Timestamp access
+
+Host chains MUST provide a current Unix timestamp, accessible with `currentTimestamp()`:
+
+```typescript
+type currentTimestamp = () => uint64
+```
+
+In order for timestamps to be used safely in timeouts, timestamps in subsequent headers MUST be non-decreasing.
+
+### Port system
+
+Host state machines MUST implement a port system, where the IBC handler can allow different modules in the host state machine to bind to uniquely named ports. Ports are identified by an `Identifier`.
+
+Host state machines MUST implement permission interaction with the IBC handler such that:
+
+- Once a module has bound to a port, no other modules can use that port until the module releases it
+- A single module can bind to multiple ports
+- Ports are allocated first-come first-serve and "reserved" ports for known modules can be bound when the state machine is first started
+
+This permissioning can be implemented with unique references (object capabilities) for each port (a la the Cosmos SDK), with source authentication (a la Ethereum), or with some other method of access control, in any case enforced by the host state machine. See [ICS 5](../ics-005-port-allocation) for details.
+
+Modules that wish to make use of particular IBC features MAY implement certain handler functions, e.g. to add additional logic to a channel handshake with an associated module on another state machine.
+
+### Datagram submission
+
+Host state machines which implement the routing module MAY define a `submitDatagram` function to submit datagrams[1](#footnote1), which will be included in transactions, directly to the routing module (defined in [ICS 26](../ics-026-routing-module)):
+
+```typescript
+type submitDatagram = (datagram: Datagram) => void
+```
+
+`submitDatagram` allows relayer processes to submit IBC datagrams directly to the routing module on the host state machine. Host state machines MAY require that the relayer process submitting the datagram has an account to pay transaction fees, signs over the datagram in a larger transaction structure, etc — `submitDatagram` MUST define & construct any such packaging required.
+
+### Exception system
+
+Host state machines MUST support an exception system, whereby a transaction can abort execution and revert any previously made state changes (including state changes in other modules happening within the same transaction), excluding gas consumed & fee payments as appropriate, and a system invariant violation can halt the state machine.
+
+This exception system MUST be exposed through two functions: `abortTransactionUnless` and `abortSystemUnless`, where the former reverts the transaction and the latter halts the state machine.
+
+```typescript
+type abortTransactionUnless = (bool) => void
+```
+
+If the boolean passed to `abortTransactionUnless` is `true`, the host state machine need not do anything. If the boolean passed to `abortTransactionUnless` is `false`, the host state machine MUST abort the transaction and revert any previously made state changes, excluding gas consumed & fee payments as appropriate.
+
+```typescript
+type abortSystemUnless = (bool) => void
+```
+
+If the boolean passed to `abortSystemUnless` is `true`, the host state machine need not do anything. If the boolean passed to `abortSystemUnless` is `false`, the host state machine MUST halt.
+
+### Data availability
+
+For deliver-or-timeout safety, host state machines MUST have eventual data availability, such that any key/value pairs in state can be eventually retrieved by relayers. For exactly-once safety, data availability is not required.
+
+For liveness of packet relay, host state machines MUST have bounded transactional liveness (and thus necessarily consensus liveness), such that incoming transactions are confirmed within a block height bound (in particular, less than the timeouts assign to the packets).
+
+IBC packet data, and other data which is not directly stored in the state vector but is relied upon by relayers, MUST be available to & efficiently computable by relayer processes.
+
+Light clients of particular consensus algorithms may have different and/or more strict data availability requirements.
+
+### Event logging system
+
+The host state machine MUST provide an event logging system whereby arbitrary data can be logged in the course of transaction execution which can be stored, indexed, and later queried by processes executing the state machine. These event logs are utilised by relayers to read IBC packet data & timeouts, which are not stored directly in the chain state (as this storage is presumed to be expensive) but are instead committed to with a succinct cryptographic commitment (only the commitment is stored).
+
+This system is expected to have at minimum one function for emitting log entries and one function for querying past logs, approximately as follows.
+
+The function `emitLogEntry` can be called by the state machine during transaction execution to write a log entry:
+
+```typescript
+type emitLogEntry = (topic: string, data: []byte) => void
+```
+
+The function `queryByTopic` can be called by an external process (such as a relayer) to retrieve all log entries associated with a given topic written by transactions which were executed at a given height.
+
+```typescript
+type queryByTopic = (height: Height, topic: string) => []byte
+```
+
+More complex query functionality MAY also be supported, and may allow for more efficient relayer process queries, but is not required.
+
+### Handling upgrades
+
+Host machines may safely upgrade parts of their state machine without disruption to IBC functionality. In order to do this safely, the IBC handler logic must remain compliant with the specification, and all IBC-related state (in both the provable & private stores) must be persisted across the upgrade. If clients exist for an upgrading chain on other chains, and the upgrade will change the light client validation algorithm, these clients must be informed prior to the upgrade so that they can safely switch atomically and preserve continuity of connections & channels.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+Key/value store functionality and consensus state type are unlikely to change during operation of a single host state machine.
+
+`submitDatagram` can change over time as relayers should be able to update their processes.
+
+## Example Implementations
+
+- Implementation of ICS 24 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 24 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Apr 29, 2019 - Initial draft
+
+May 11, 2019 - Rename "RootOfTrust" to "ConsensusState"
+
+Jun 25, 2019 - Use "ports" instead of module names
+
+Aug 18, 2019 - Revisions to module system, definitions
+
+Jul 05, 2022 - Lower the minimal allowed length of a channel identifier to 8
+
+Jul 27, 2022 - Move `ClientState` to the `provableStore`, and add "Client state validation" section
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
+
+---
+
+: A datagram is an opaque bytestring transmitted over some physical network, and handled by the IBC routing module implemented in the ledger's state machine. In some implementations, the datagram may be a field in a ledger-specific transaction or message data structure which also contains other information (e.g. a fee for spam prevention, nonce for replay prevention, type identifier to route to the IBC handler, etc.). All IBC sub-protocols (such as opening a connection, creating a channel, sending a packet) are defined in terms of sets of datagrams and protocols for handling them through the routing module.
diff --git a/ibc/next/spec/core/ics-025-handler-interface/README.mdx b/ibc/next/spec/core/ics-025-handler-interface/README.mdx
new file mode 100644
index 00000000..547f7ed4
--- /dev/null
+++ b/ibc/next/spec/core/ics-025-handler-interface/README.mdx
@@ -0,0 +1,92 @@
+---
+ics: 25
+title: Handler Interface
+stage: draft
+category: IBC/TAO
+kind: instantiation
+requires: [2, 3, 4, 23, 24]
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-04-23
+modified: 2019-08-25
+---
+
+
+## Synopsis
+
+This document describes the interface exposed by the standard IBC implementation (referred to as the IBC handler) to modules within the same state machine, and the implementation of that interface by the IBC handler.
+
+### Motivation
+
+IBC is an inter-module communication protocol, designed to facilitate reliable, authenticated message passing between modules on separate blockchains. Modules should be able to reason about the interface they interact with and the requirements they must adhere to in order to utilise the interface safely.
+
+### Definitions
+
+Associated definitions are as defined in referenced prior standards (where the functions are defined), where appropriate.
+
+### Desired Properties
+
+- Creation of clients, connections, and channels should be as permissionless as possible.
+- The module set should be dynamic: chains should be able to add and destroy modules, which can themselves bind to and unbind from ports, at will with a persistent IBC handler.
+- Modules should be able to write their own more complex abstractions on top of IBC to provide additional semantics or guarantees.
+
+## Technical Specification
+
+> Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability key parameter.
+
+### Client lifecycle management
+
+By default, clients are unowned: any module may create a new client, query any existing client, update any existing client, and delete any existing client not in use.
+
+The handler interface exposes `createClient`, `updateClient`, `queryConsensusState`, `queryClientState`, and `submitMisbehaviourToClient` as defined in [ICS 2](../ics-002-client-semantics).
+
+### Connection lifecycle management
+
+The handler interface exposes `connOpenInit`, `connOpenTry`, `connOpenAck`, `connOpenConfirm`, and `queryConnection`, as defined in [ICS 3](../ics-003-connection-semantics).
+
+The default IBC routing module SHALL allow external calls to `connOpenTry`, `connOpenAck`, and `connOpenConfirm`.
+
+### Channel lifecycle management
+
+By default, channels are owned by the creating port, meaning only the module bound to that port is allowed to inspect, close, or send on the channel. A module can create any number of channels utilising the same port.
+
+The handler interface exposes `chanOpenInit`, `chanOpenTry`, `chanOpenAck`, `chanOpenConfirm`, `chanCloseInit`, `chanCloseConfirm`, and `queryChannel`, as defined in [ICS 4](../ics-004-channel-and-packet-semantics).
+
+The default IBC routing module SHALL allow external calls to `chanOpenTry`, `chanOpenAck`, `chanOpenConfirm`, and `chanCloseConfirm`.
+
+### Packet relay
+
+Packets are permissioned by channel (only a port which owns a channel can send or receive on it).
+
+The handler interface exposes `sendPacket`, `recvPacket`, `acknowledgePacket`, `timeoutPacket`, and `timeoutOnClose` as defined in [ICS 4](../ics-004-channel-and-packet-semantics).
+
+The default IBC routing module SHALL allow external calls to `sendPacket`, `recvPacket`, `acknowledgePacket`, `timeoutPacket`, and `timeoutOnClose`.
+
+### Properties & Invariants
+
+The IBC handler module interface as defined here inherits properties of functions as defined in their associated specifications.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+This interface MAY change when implemented on new chains (or upgrades to an existing chain) as long as the semantics remain the same.
+
+## Example Implementations
+
+Coming soon.
+
+## History
+
+Jun 9, 2019 - Draft written
+
+Aug 24, 2019 - Revisions, cleanup
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-026-routing-module/README.mdx b/ibc/next/spec/core/ics-026-routing-module/README.mdx
new file mode 100644
index 00000000..cc449bba
--- /dev/null
+++ b/ibc/next/spec/core/ics-026-routing-module/README.mdx
@@ -0,0 +1,827 @@
+---
+ics: 26
+title: Routing Module
+stage: draft
+category: IBC/TAO
+kind: instantiation
+version_compatibility:
+ - ibc-go v7.0.0
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-06-09
+modified: 2019-08-25
+---
+
+
+## Synopsis
+
+The routing module is a default implementation of a secondary module which will accept external datagrams and call into the interblockchain communication protocol handler to deal with handshakes and packet relay.
+The routing module keeps a lookup table of modules, which it can use to look up and call a module when a packet is received, so that external relayers need only ever relay packets to the routing module.
+
+### Motivation
+
+The default IBC handler uses a receiver call pattern, where modules must individually call the IBC handler in order to bind to ports, start handshakes, accept handshakes, send and receive packets, etc. This is flexible and simple but is a bit tricky to understand and may require extra work on the part of relayer processes, who must track the state of many modules. This standard describes an IBC "routing module" to automate most common functionality, route packets, and simplify the task of relayers.
+
+The routing module can also play the role of the module manager as discussed in [ICS 5](../ics-005-port-allocation) and implement
+logic to determine when modules are allowed to bind to ports and what those ports can be named.
+
+### Definitions
+
+All functions provided by the IBC handler interface are defined as in [ICS 25](../ics-025-handler-interface).
+
+The functions `newCapability` & `authenticateCapability` are defined as in [ICS 5](../ics-005-port-allocation).
+
+The functions `writeChannel` and `writeAcknowledgement` are defined as in [ICS 4](../ics-004-channel-and-packet-semantics)
+
+### Desired Properties
+
+- Modules should be able to bind to ports and own channels through the routing module.
+- No overhead should be added for packet sends and receives other than the layer of call indirection.
+- The routing module should call specified handler functions on modules when they need to act upon packets.
+
+## Technical Specification
+
+> Note: If the host state machine is utilising object capability authentication (see [ICS 005](../ics-005-port-allocation)), all functions utilising ports take an additional capability parameter.
+
+### Module callback interface
+
+Modules must expose the following function signatures to the routing module, which are called upon the receipt of various datagrams:
+
+#### **OnChanOpenInit**
+
+`onChanOpenInit` will verify that the relayer-chosen parameters
+are valid and perform any custom `INIT` logic.
+It may return an error if the chosen parameters are invalid
+in which case the handshake is aborted.
+If the provided version string is non-empty, `onChanOpenInit` should return
+the version string or an error if the provided version is invalid.
+If the version string is empty, `onChanOpenInit` is expected to
+return a default version string representing the version(s)
+it supports.
+If there is no default version string for the application,
+it should return an error if provided version is empty string.
+
+```typescript
+function onChanOpenInit(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ version: string) => (version: string, err: Error) {
+ // defined by the module
+}
+```
+
+#### **OnChanOpenTry**
+
+`onChanOpenTry` will verify the INIT-chosen parameters along with the
+counterparty-chosen version string and perform custom `TRY` logic.
+If the INIT-chosen parameters
+are invalid, the callback must return an error to abort the handshake.
+If the counterparty-chosen version is not compatible with this modules
+supported versions, the callback must return an error to abort the handshake.
+If the versions are compatible, the try callback must select the final version
+string and return it to core IBC.
+`onChanOpenTry` may also perform custom initialization logic
+
+```typescript
+function onChanOpenTry(
+ order: ChannelOrder,
+ connectionHops: [Identifier],
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyPortIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) => (version: string, err: Error) {
+ // defined by the module
+}
+```
+
+#### **OnChanOpenAck**
+
+`onChanOpenAck` will error if the counterparty selected version string
+is invalid to abort the handshake. It may also perform custom ACK logic.
+
+```typescript
+function onChanOpenAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyChannelIdentifier: Identifier,
+ counterpartyVersion: string) {
+ // defined by the module
+}
+```
+
+#### **OnChanOpenConfirm**
+
+`onChanOpenConfirm` will perform custom CONFIRM logic and may error to abort the handshake.
+
+```typescript
+function onChanOpenConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // defined by the module
+}
+```
+
+```typescript
+function onChanCloseInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // defined by the module
+}
+
+function onChanCloseConfirm(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier): void {
+ // defined by the module
+}
+
+function onRecvPacket(packet: Packet, relayer: string): bytes {
+ // defined by the module, returns acknowledgement
+}
+
+function onTimeoutPacket(packet: Packet, relayer: string) {
+ // defined by the module
+}
+
+function onAcknowledgePacket(packet: Packet, acknowledgement: bytes, relayer: string) {
+ // defined by the module
+}
+
+function onTimeoutPacketClose(packet: Packet, relayer: string) {
+ // defined by the module
+}
+```
+
+Exceptions MUST be thrown to indicate failure and reject the handshake, incoming packet, etc.
+
+These are combined together in a `ModuleCallbacks` interface:
+
+```typescript
+interface ModuleCallbacks {
+ onChanOpenInit: onChanOpenInit
+ onChanOpenTry: onChanOpenTry
+ onChanOpenAck: onChanOpenAck
+ onChanOpenConfirm: onChanOpenConfirm
+ onChanCloseInit: onChanCloseInit
+ onChanCloseConfirm: onChanCloseConfirm
+ onRecvPacket: onRecvPacket
+ onTimeoutPacket: onTimeoutPacket
+ onAcknowledgePacket: onAcknowledgePacket
+ onTimeoutPacketClose: onTimeoutPacketClose
+}
+```
+
+Callbacks are provided when the module binds to a port.
+
+```typescript
+function callbackPath(portIdentifier: Identifier): Path {
+ return "callbacks/{portIdentifier}"
+}
+```
+
+The calling module identifier is also stored for future authentication should the callbacks need to be altered.
+
+```typescript
+function authenticationPath(portIdentifier: Identifier): Path {
+ return "authentication/{portIdentifier}"
+}
+```
+
+### Port binding as module manager
+
+The IBC routing module sits in-between the handler module ([ICS 25](../ics-025-handler-interface)) and individual modules on the host state machine.
+
+The routing module, acting as a module manager, differentiates between two kinds of ports:
+
+- "Existing name” ports: e.g. “bank”, with standardised prior meanings, which should not be first-come-first-serve
+- “Fresh name” ports: new identity (perhaps a smart contract) w/no prior relationships, new random number port, post-generation port name can be communicated over another channel
+
+A set of existing names are allocated, along with corresponding modules, when the routing module is instantiated by the host state machine.
+The routing module then allows allocation of fresh ports at any time by modules, but they must use a specific standardised prefix.
+
+The function `bindPort` can be called by a module in order to bind to a port, through the routing module, and set up callbacks.
+
+```typescript
+function bindPort(
+ id: Identifier,
+ callbacks: Callbacks): CapabilityKey {
+ abortTransactionUnless(privateStore.get(callbackPath(id)) === null)
+ privateStore.set(callbackPath(id), callbacks)
+ capability = handler.bindPort(id)
+ claimCapability(authenticationPath(id), capability)
+ return capability
+}
+```
+
+The function `updatePort` can be called by a module in order to alter the callbacks.
+
+```typescript
+function updatePort(
+ id: Identifier,
+ capability: CapabilityKey,
+ newCallbacks: Callbacks) {
+ abortTransactionUnless(authenticateCapability(authenticationPath(id), capability))
+ privateStore.set(callbackPath(id), newCallbacks)
+}
+```
+
+The function `releasePort` can be called by a module in order to release a port previously in use.
+
+> Warning: releasing a port will allow other modules to bind to that port and possibly intercept incoming channel opening handshakes. Modules should release ports only when doing so is safe.
+
+```typescript
+function releasePort(
+ id: Identifier,
+ capability: CapabilityKey) {
+ abortTransactionUnless(authenticateCapability(authenticationPath(id), capability))
+ handler.releasePort(id)
+ privateStore.delete(callbackPath(id))
+ privateStore.delete(authenticationPath(id))
+}
+```
+
+The function `lookupModule` can be used by the routing module to lookup the callbacks bound to a particular port.
+
+```typescript
+function lookupModule(portId: Identifier) {
+ return privateStore.get(callbackPath(portId))
+}
+```
+
+### Datagram handlers (write)
+
+*Datagrams* are external data blobs accepted as transactions by the routing module. This section defines a *handler function* for each datagram,
+which is executed when the associated datagram is submitted to the routing module in a transaction.
+
+All datagrams can also be safely submitted by other modules to the routing module.
+
+No message signatures or data validity checks are assumed beyond those which are explicitly indicated.
+
+#### Client lifecycle management
+
+`ClientCreate` creates a new light client with the specified identifier & consensus state.
+
+```typescript
+interface ClientCreate {
+ identifier: Identifier
+ clientState: ClientState
+ consensusState: ConsensusState
+}
+```
+
+```typescript
+function handleClientCreate(datagram: ClientCreate) {
+ handler.createClient(datagram.clientState, datagram.consensusState)
+}
+```
+
+`ClientUpdate` updates an existing light client with the specified identifier & new header.
+
+```typescript
+interface ClientUpdate {
+ identifier: Identifier
+ header: Header
+}
+```
+
+```typescript
+function handleClientUpdate(datagram: ClientUpdate) {
+ handler.updateClient(datagram.identifier, datagram.header)
+}
+```
+
+`ClientSubmitMisbehaviour` submits proof-of-misbehaviour to an existing light client with the specified identifier.
+
+```typescript
+interface ClientMisbehaviour {
+ identifier: Identifier
+ evidence: bytes
+}
+```
+
+```typescript
+function handleClientMisbehaviour(datagram: ClientMisbehaviour) {
+ handler.submitMisbehaviourToClient(datagram.identifier, datagram.evidence)
+}
+```
+
+#### Connection lifecycle management
+
+The `ConnOpenInit` datagram starts the connection handshake process with an IBC module on another chain.
+
+```typescript
+interface ConnOpenInit {
+ counterpartyPrefix: CommitmentPrefix
+ clientIdentifier: Identifier
+ counterpartyClientIdentifier: Identifier
+ version: string
+ delayPeriodTime: uint64
+ delayPeriodBlocks: uint64
+}
+```
+
+```typescript
+function handleConnOpenInit(datagram: ConnOpenInit) {
+ handler.connOpenInit(
+ datagram.counterpartyPrefix,
+ datagram.clientIdentifier,
+ datagram.counterpartyClientIdentifier,
+ datagram.version,
+ datagram.delayPeriodTime,
+ datagram.delayPeriodBlocks
+ )
+}
+```
+
+The `ConnOpenTry` datagram accepts a handshake request from an IBC module on another chain.
+
+```typescript
+interface ConnOpenTry {
+ counterpartyConnectionIdentifier: Identifier
+ counterpartyPrefix: CommitmentPrefix
+ counterpartyClientIdentifier: Identifier
+ clientIdentifier: Identifier
+ clientState: ClientState // DEPRECATED
+ counterpartyVersions: string[]
+ delayPeriodTime: uint64
+ delayPeriodBlocks: uint64
+ proofInit: CommitmentProof
+ proofClient: CommitmentProof // DEPRECATED
+ proofConsensus: CommitmentProof // DEPRECATED
+ proofHeight: Height
+ consensusHeight: Height // DEPRECATED
+}
+```
+
+```typescript
+function handleConnOpenTry(datagram: ConnOpenTry) {
+ handler.connOpenTry(
+ datagram.counterpartyConnectionIdentifier,
+ datagram.counterpartyPrefix,
+ datagram.counterpartyClientIdentifier,
+ datagram.clientIdentifier,
+ datagram.clientState,
+ datagram.counterpartyVersions,
+ datagram.delayPeriodTime,
+ datagram.delayPeriodBlocks,
+ datagram.proofInit,
+ datagram.proofClient,
+ datagram.proofConsensus,
+ datagram.proofHeight,
+ datagram.consensusHeight
+ )
+}
+```
+
+The `ConnOpenAck` datagram confirms a handshake acceptance by the IBC module on another chain.
+
+```typescript
+interface ConnOpenAck {
+ identifier: Identifier
+ clientState: ClientState // DEPRECATED
+ version: string
+ counterpartyIdentifier: Identifier
+ proofTry: CommitmentProof
+ proofClient: CommitmentProof // DEPRECATED
+ proofConsensus: CommitmentProof // DEPRECATED
+ proofHeight: Height
+ consensusHeight: Height // DEPRECATED
+}
+```
+
+```typescript
+function handleConnOpenAck(datagram: ConnOpenAck) {
+ handler.connOpenAck(
+ datagram.identifier,
+ datagram.clientState,
+ datagram.version,
+ datagram.counterpartyIdentifier,
+ datagram.proofTry,
+ datagram.proofClient,
+ datagram.proofConsensus,
+ datagram.proofHeight,
+ datagram.consensusHeight
+ )
+}
+```
+
+The `ConnOpenConfirm` datagram acknowledges a handshake acknowledgement by an IBC module on another chain & finalises the connection.
+
+```typescript
+interface ConnOpenConfirm {
+ identifier: Identifier
+ proofAck: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handleConnOpenConfirm(datagram: ConnOpenConfirm) {
+ handler.connOpenConfirm(
+ datagram.identifier,
+ datagram.proofAck,
+ datagram.proofHeight
+ )
+}
+```
+
+#### Channel lifecycle management
+
+```typescript
+interface ChanOpenInit {
+ order: ChannelOrder
+ connectionHops: [Identifier]
+ portIdentifier: Identifier
+ counterpartyPortIdentifier: Identifier
+ version: string
+}
+```
+
+```typescript
+function handleChanOpenInit(datagram: ChanOpenInit) {
+ module = lookupModule(datagram.portIdentifier)
+ channelIdentifier, channelCapability = handler.chanOpenInit(
+ datagram.order,
+ datagram.connectionHops,
+ datagram.portIdentifier,
+ datagram.counterpartyPortIdentifier
+ )
+ // SYNCHRONOUS: the following calls happen synchronously with the call above
+ // ASYNCHRONOUS: the module callback will be called at a time later than the channel handler
+ // in this case, the channel identifier will be stored with a sentinel value in the channel path so it is not taken
+ // by a new channel handshake and the capability is reserved for the application module.
+ // When the module eventually executes its callback it must call writeChannel so that the channel
+ // can be written into an INIT state with the right version and the handshake can proceed on the counterparty.
+ version, err = module.onChanOpenInit(
+ datagram.order,
+ datagram.connectionHops,
+ datagram.portIdentifier,
+ channelIdentifier,
+ datagram.counterpartyPortIdentifier,
+ datagram.version
+ )
+ abortTransactionUnless(err === nil)
+ writeChannel(
+ datagram.portIdentifier,
+ channelIdentifier,
+ INIT,
+ datagram.order,
+ datagram.counterpartyPortIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.connectionHops,
+ version
+ )
+}
+```
+
+```typescript
+interface ChanOpenTry {
+ order: ChannelOrder
+ connectionHops: [Identifier]
+ portIdentifier: Identifier
+ channelIdentifier: Identifier
+ counterpartyPortIdentifier: Identifier
+ counterpartyChannelIdentifier: Identifier
+ counterpartyVersion: string
+ proofInit: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handleChanOpenTry(datagram: ChanOpenTry) {
+ module = lookupModule(datagram.portIdentifier)
+ channelIdentifier, channelCapability = handler.chanOpenTry(
+ datagram.order,
+ datagram.connectionHops,
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ datagram.counterpartyPortIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.counterpartyVersion,
+ datagram.proofInit,
+ datagram.proofHeight
+ )
+ // SYNCHRONOUS: the following calls happen synchronously with the call above
+ // ASYNCHRONOUS: the module callback will be called at a time later than the channel handler
+ // in this case, the channel identifier will be stored with a sentinel value in the channel path so it is not taken
+ // by a new channel handshake and the capability is reserved for the application module.
+ // When the module eventually executes its callback it must call writeChannel so that the channel
+ // can be written into a TRY state with the right version and the handshake can proceed on the counterparty.
+ version, err = module.onChanOpenTry(
+ datagram.order,
+ datagram.connectionHops,
+ datagram.portIdentifier,
+ channelIdentifier,
+ datagram.counterpartyPortIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.counterpartyVersion
+ )
+ abortTransactionUnless(err === nil)
+ writeChannel(
+ datagram.portIdentifier,
+ channelIdentifier,
+ TRYOPEN,
+ datagram.order,
+ datagram.counterpartyPortIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.connectionHops,
+ version
+ )
+}
+```
+
+```typescript
+interface ChanOpenAck {
+ portIdentifier: Identifier
+ channelIdentifier: Identifier
+ counterpartyChannelIdentifier: Identifier
+ counterpartyVersion: string
+ proofTry: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handleChanOpenAck(datagram: ChanOpenAck) {
+ module = lookupModule(datagram.portIdentifier)
+ handler.chanOpenAck(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.counterpartyVersion,
+ datagram.proofTry,
+ datagram.proofHeight
+ )
+ // SYNCHRONOUS: the following calls happen synchronously with the call above
+ // ASYNCHRONOUS: the module callback will be called at a time later than the channel handler
+ // When the module eventually executes its callback it must call writeChannel so that the channel
+ // can be written into an OPEN state and the handshake can proceed on the counterparty.
+ err = module.onChanOpenAck(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ datagram.counterpartyVersion
+ )
+ abortTransactionUnless(err === nil)
+ channel = provableStore.get(channelPath(datagram.portIdentifier, datagram.channelIdentifier))
+ writeChannel(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ OPEN,
+ channel.order,
+ channel.counterparty.portIdentifier,
+ datagram.counterpartyChannelIdentifier,
+ channel.connectionHops,
+ datagram.counterpartyVersion
+ )
+}
+```
+
+```typescript
+interface ChanOpenConfirm {
+ portIdentifier: Identifier
+ channelIdentifier: Identifier
+ proofAck: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handleChanOpenConfirm(datagram: ChanOpenConfirm) {
+ module = lookupModule(datagram.portIdentifier)
+ handler.chanOpenConfirm(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ datagram.proofAck,
+ datagram.proofHeight
+ )
+ // SYNCHRONOUS: the following calls happen synchronously with the call above
+ // ASYNCHRONOUS: the module callback will be called at a time later than the channel handler
+ // When the module eventually executes its callback it must call writeChannel so that the channel
+ // can be written into an OPEN state and the handshake can proceed on the counterparty.
+ err = module.onChanOpenConfirm(
+ datagram.portIdentifier,
+ datagram.channelIdentifier
+ )
+ abortTransactionUnless(err === nil)
+ channel = provableStore.get(channelPath(datagram.portIdentifier, datagram.channelIdentifier))
+ writeChannel(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ OPEN,
+ channel.order,
+ channel.counterparty.portIdentifier,
+ channel.counterparty.channelIdentifier,
+ channel.connectionHops,
+ channel.version
+ )
+}
+```
+
+```typescript
+interface ChanCloseInit {
+ portIdentifier: Identifier
+ channelIdentifier: Identifier
+}
+```
+
+```typescript
+function handleChanCloseInit(datagram: ChanCloseInit) {
+ module = lookupModule(datagram.portIdentifier)
+ err = module.onChanCloseInit(
+ datagram.portIdentifier,
+ datagram.channelIdentifier
+ )
+ abortTransactionUnless(err === nil)
+ handler.chanCloseInit(
+ datagram.portIdentifier,
+ datagram.channelIdentifier
+ )
+}
+```
+
+```typescript
+interface ChanCloseConfirm {
+ portIdentifier: Identifier
+ channelIdentifier: Identifier
+ proofInit: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handleChanCloseConfirm(datagram: ChanCloseConfirm) {
+ handler.chanCloseConfirm(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ datagram.proofInit,
+ datagram.proofHeight
+ )
+ // SYNCHRONOUS: the following calls happen synchronously with the call above
+ // ASYNCHRONOUS: the module callback will be called at a time later than the channel handler
+ // When the module eventually executes its callback it must call writeChannel so that the channel
+ // can be written into a CLOSED state and the handshake can proceed on the counterparty.
+ module = lookupModule(datagram.portIdentifier)
+ err = module.onChanCloseConfirm(
+ datagram.portIdentifier,
+ datagram.channelIdentifier
+ )
+ abortTransactionUnless(err === nil)
+ writeChannel(
+ datagram.portIdentifier,
+ datagram.channelIdentifier,
+ CLOSED,
+ )
+}
+```
+
+#### Packet relay
+
+Packets are sent by the module directly (by the module calling the IBC handler).
+
+```typescript
+interface PacketRecv {
+ packet: Packet
+ proof: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handlePacketRecv(datagram: PacketRecv) {
+ module = lookupModule(datagram.packet.destPort)
+ handler.recvPacket(
+ datagram.packet,
+ datagram.proof,
+ datagram.proofHeight,
+ acknowledgement
+ )
+ acknowledgement = module.onRecvPacket(datagram.packet)
+ writeAcknowledgement(datagram.packet, acknowledgement)
+}
+```
+
+```typescript
+interface PacketAcknowledgement {
+ packet: Packet
+ acknowledgement: string
+ proof: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handlePacketAcknowledgement(datagram: PacketAcknowledgement) {
+ module = lookupModule(datagram.packet.sourcePort)
+ handler.acknowledgePacket(
+ datagram.packet,
+ datagram.acknowledgement,
+ datagram.proof,
+ datagram.proofHeight
+ )
+ module.onAcknowledgePacket(
+ datagram.packet,
+ datagram.acknowledgement
+ )
+}
+```
+
+#### Packet timeouts
+
+```typescript
+interface PacketTimeout {
+ packet: Packet
+ proof: CommitmentProof
+ proofHeight: Height
+ nextSequenceRecv: Maybe
+}
+```
+
+```typescript
+function handlePacketTimeout(datagram: PacketTimeout) {
+ module = lookupModule(datagram.packet.sourcePort)
+ handler.timeoutPacket(
+ datagram.packet,
+ datagram.proof,
+ datagram.proofHeight,
+ datagram.nextSequenceRecv
+ )
+ module.onTimeoutPacket(datagram.packet)
+}
+```
+
+```typescript
+interface PacketTimeoutOnClose {
+ packet: Packet
+ proof: CommitmentProof
+ proofHeight: Height
+}
+```
+
+```typescript
+function handlePacketTimeoutOnClose(datagram: PacketTimeoutOnClose) {
+ module = lookupModule(datagram.packet.sourcePort)
+ handler.timeoutOnClose(
+ datagram.packet,
+ datagram.proof,
+ datagram.proofHeight
+ )
+ module.onTimeoutPacket(datagram.packet)
+}
+```
+
+#### Closure-by-timeout & packet cleanup
+
+```typescript
+interface PacketCleanup {
+ packet: Packet
+ proof: CommitmentProof
+ proofHeight: Height
+ nextSequenceRecvOrAcknowledgement: Either
+}
+```
+
+### Query (read-only) functions
+
+All query functions for clients, connections, and channels should be exposed (read-only) directly by the IBC handler module.
+
+### Interface usage example
+
+See [ICS 20](../../app/ics-020-fungible-token-transfer) for a usage example.
+
+### Properties & Invariants
+
+- Proxy port binding is first-come-first-serve: once a module binds to a port through the IBC routing module, only that module can utilise that port until the module releases it.
+
+## Backwards Compatibility
+
+Not applicable.
+
+## Forwards Compatibility
+
+routing modules are closely tied to the IBC handler interface.
+
+## Example Implementations
+
+- Implementation of ICS 26 in Go can be found in [ibc-go repository](https://github.com/cosmos/ibc-go).
+- Implementation of ICS 26 in Rust can be found in [ibc-rs repository](https://github.com/cosmos/ibc-rs).
+
+## History
+
+Jun 9, 2019 - Draft submitted
+
+Jul 28, 2019 - Major revisions
+
+Aug 25, 2019 - Major revisions
+
+Mar 28, 2023 - Fix order of executing module handler and application callback
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-026-routing-module/UPGRADES.mdx b/ibc/next/spec/core/ics-026-routing-module/UPGRADES.mdx
new file mode 100644
index 00000000..26b413ae
--- /dev/null
+++ b/ibc/next/spec/core/ics-026-routing-module/UPGRADES.mdx
@@ -0,0 +1,108 @@
+---
+title: "Application Upgrade Callbacks"
+---
+
+## Synopsis
+
+This standard document specifies the interfaces and state machine logic that IBC applications must implement in order to enable existing channels to upgrade their applications after the initial channel handshake.
+
+### Motivation
+
+As new features get added to IBC applications, chains may wish to take advantage of new application features without abandoning the accumulated state and network effect(s) of an already existing channel. The upgrade protocol proposed would allow applications to renegotiate an existing channel to take advantage of new features without having to create a new channel, thus preserving all existing application state while upgrading to new application logic.
+
+### Desired Properties
+
+- Both applications MUST agree to the renegotiated application parameters.
+- Application state and logic on both chains SHOULD either be using the old parameters or the new parameters, but MUST NOT be in an in-between state, e.g., it MUST NOT be possible for an application to run v2 logic, while its counterparty is still running v1 logic.
+- The application upgrade protocol is atomic, i.e.,
+ - either it is unsuccessful and then the application MUST fall-back to the original application parameters;
+ - or it is successful and then both applications MUST adopt the new application parameters and the applications must process packet data appropriately.
+- The application must be able to maintain several different supported versions such that one channel may be on version `v1` and another channel may be on version `v2` and the application can handle the channel state and logic accordingly depending on the application version for the respective channel.
+
+The application upgrade protocol MUST NOT modify the channel identifiers.
+
+## Technical Specification
+
+In order to support channel upgrades, the application must implement the following interface:
+
+```typescript
+interface ModuleUpgradeCallbacks {
+ onChanUpgradeInit: onChanUpgradeInit, // read-only
+ onChanUpgradeTry: onChanUpgradeTry, // read-only
+ onChanUpgradeAck: onChanUpgradeAck, // read-only
+ onChanUpgradeOpen: onChanUpgradeOpen
+}
+```
+
+### **OnChanUpgradeInit**
+
+`onChanUpgradeInit` will verify that the upgrade parameters
+are valid and perform any custom `UpgradeInit` logic.
+It may return an error if the chosen parameters are invalid
+in which case the upgrade handshake is aborted.
+The callback is provided the new upgrade parameters. It may perform the necessary checks to ensure that it can support the new channel parameters. If upgrading the application from the previous parameters to the new parameters is not supported, it must return an error.
+
+`onChanUpgradeInit` may return a modified version to be stored in the upgrade. This may occur if the application needs to store some metadata in the version string as part of its channel negotiation.
+
+If an error is returned, then core IBC will abort the handshake.
+
+`onChanUpgradeInit` MUST NOT write any state changes as this will be done only once the upgrade is completed and confirmed to succeed on both sides
+
+```typescript
+function onChanUpgradeInit(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proposedOrdering: ChannelOrder,
+ proposedConnectionHops: [Identifier],
+ proposedVersion: string) => (version: string, err: Error) {
+ // defined by the module
+} (version: string)
+```
+
+### **OnChanUpgradeTry**
+
+`onChanUpgradeTry` will verify the upgrade-chosen parameters from the counterparty.
+If the upgrade-chosen parameters are unsupported by the application, the callback must return an error to abort the handshake.
+The try callback may return a modified version, in case it needs to add some metadata to the version string.
+This will be stored as the final proposed version of the upgrade by core IBC.
+
+`onChanUpgradeTry` MUST NOT write any state changes as this will be done only once the upgrade is completed and confirmed to succeed on both sides.
+
+```typescript
+function onChanUpgradeTry(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ proposedOrdering: ChannelOrder,
+ proposedConnectionHops: [Identifier],
+ proposedVersion: string) => (version: string, err: Error) {
+ // defined by the module
+} (version: string)
+```
+
+### **OnChanUpgradeAck**
+
+`onChanUpgradeAck` will error if the counterparty selected version string
+is unsupported. If an error is returned by the callback, core IBC will abort the handshake.
+
+`onChanUpgradeAck` MUST NOT write any state changes as this will be done only once the upgrade is completed and confirmed to succeed on both sides.
+
+```typescript
+function onChanUpgradeAck(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier,
+ counterpartyVersion: string) => Error {
+ // defined by the module
+}
+```
+
+### **OnChanUpgradeOpen**
+
+`onChanUpgradeOpen` is called after the upgrade is complete and both sides are guaranteed to move to the new channel parameters. Thus, the application may now perform any state migrations necessary to start supporting packet processing according to the new channel parameters.
+
+```typescript
+function onChanUpgradeOpen(
+ portIdentifier: Identifier,
+ channelIdentifier: Identifier) {
+ // defined by the module
+}
+```
diff --git a/ibc/next/spec/core/ics-033-multi-hop/README.mdx b/ibc/next/spec/core/ics-033-multi-hop/README.mdx
new file mode 100644
index 00000000..07becdfa
--- /dev/null
+++ b/ibc/next/spec/core/ics-033-multi-hop/README.mdx
@@ -0,0 +1,583 @@
+---
+ics: 33
+title: Multi-hop Channel
+stage: draft
+category: IBC/TAO
+kind: instantiation
+required_by: [4]
+authors:
+ - name: Derek Shiell
+ email: derek@polymerlabs.org
+ - name: Wenwei Xiong
+ email: wenwei@polymerlabs.org
+ - name: Bo Du
+ email: bo@polymerlabs.org
+created: 2022-11-11
+modified: 2022-12-16
+---
+
+## Synopsis
+
+This document describes a standard for multi-hop IBC channels. Multi-hop channels specify a way to route messages across a path of IBC enabled blockchains utilizing multiple pre-existing IBC connections.
+
+### Motivation
+
+The current IBC protocol defines messaging in a point-to-point paradigm which allows message passing between two directly connected IBC chains, but as more IBC enabled chains come into existence there becomes a need to relay IBC packets across chains because IBC connections may not exist between the two chains wishing to exchange messages. IBC connections may not exist for a variety of reasons which could include economic inviability since connections require client state to be continuously exchanged between connection ends which carries a cost.
+
+### Definitions
+
+Associated definitions are as defined in referenced prior standards (where the functions are defined), where appropriate.
+
+`Connection` is as defined in [ICS 3](../ics-003-connection-semantics).
+
+`Channel` is as defined in [ICS 4](../ics-004-channel-and-packet-semantics).
+
+`Channel Path` is defined as the path of connection IDs along which a channel is defined.
+
+`Connection Hop` is defined as the connection ID of the connection between two chains along a channel path.
+
+### Desired Properties
+
+- IBC channel handshake and message packets should be able to utilize pre-existing connections to form a logical proof chain to relay messages between unconnected chains.
+- Relaying for a multi-hop IBC channel should NOT require writing additional channel, packet, or timeout state to intermediate hops.
+- The design should strive to minimize the number of required client updates to generate and query multi-hop proofs.
+- Minimal additional required state and changes to core and app IBC specs.
+- Retain desired properties of connection, channel and packet definitions.
+- Retain backwards compatibility for messaging over a single connection hop.
+
+## Technical Specification
+
+The bulk of the spec describes multi-hop proof generation and verification. IBC connections remain unchanged. Additionally, channel handshake and packet message types as well as general round trip messaging semantics and flow will remain the same. There is additional work on the verifier side on the receiving chain as well as the relayers who need to query for proofs.
+
+Messages passed over multiple hops require proof of the connection path from source chain to receiving chain as well as the packet commitment on the source chain. The connection path is proven by verifying the connection state and consensus state of each connection in the path to the receiving chain. On a high level, this can be thought of as a chained proof over a channel path which enables the receiving chain to prove a key/value on the source chain by iteratively proving each connection and consensus state in the channel path starting with the consensus state associated with the final client on the receiving chain. Each subsequent consensus state and connection is proven until the source chain's consensus state is proven which can then be used to prove the desired key/value on the source chain.
+
+## Assumptions
+
+This multi-hop spec assumes that all proof specs for membership verification for each chain are equal.
+
+### Channel Handshake and Packet Messages
+
+For both channel handshake and packet messages, additional connection hops are defined in the pre-existing `connectionHops` field. The connections along the channel path must exist in the `OPEN` state to guarantee delivery to the correct recipient. See `Path Forgery Protection` for more information.
+
+See [ICS 4](../ics-004-channel-and-packet-semantics) for multi-hop related spec changes. Multi-hop does not change existing spec behavior for channel handshakes, packet delivery, and timeout handling. However, multi-hop channels require special handling for frozen clients (see `chanCloseFrozen`).
+
+In terms of connection topology, a user would be able to determine a viable channel path from sender -> receiver using information from the [chain registry](https://github.com/cosmos/chain-registry). They can also independently verify this information via network queries.
+
+### Multihop Relaying
+
+Relayers deliver channel handshake and IBC packets as they currently do except that they are required to provide proof of the channel path. Relayers scan packet events for the `connectionHops` field and determine if the packet is multi-hop by checking the number of hops in the field. If the number of hops is greater than one then the packet is a multi-hop packet and will need extra proof data.
+
+For each multi-hop channel (detailed proof logic below):
+
+1. Scan source chain for IBC messages to relay.
+2. Read the `connectionHops` field in from the scanned message to determine the channel path.
+3. Using connection endpoints via chain registry configuration, query for required multi-hop proof heights and update client states along the channel path as necessary (see pseudocode implementation).
+4. Query proof of packet or handshake message commitments on source chain at the proof height used in step 3.
+5. Query for proof of connection, and consensus state for each intermediate connection in the channel path using proof heights determined in step 3.
+6. Submit proofs and data to RPC endpoint on receiving chain.
+
+Relayers are connection topology aware with configurations sourced from the [chain registry](https://github.com/cosmos/chain-registry).
+
+### Proof Generation & Verification
+
+
+Graphical depiction of proof generation.
+
+
+During the first phase of querying a multi-hop proof the relayer searches for the minimum consensus height that can prove the previous chain state and is also provable by the next chain in the channel path.
+
+
+In the second phase of querying a multi-hop proof, the relayer queries proofs of the client and connection states as well as the key/value on the source chain at the heights determined in the first phase.
+
+
+Multi-hop proof query algorithm.
+
+
+Multi-hop proof verification logic.
+
+Proof generation pseudocode proof generation for a channel path with `N` chains: `C[0] --> C[i] --> C[N]`
+
+```go
+
+// Proof generation helper functions
+//
+// Note: 'Chain' is assumed to contain information about the next chain in the channel path
+//
+// GetClientID returns the clientID for the next chain in the channel path
+func (Chain) GetClientID() (clientID string)
+// GetConnectionID returns the connectionID corresponding to the next chain in the channel path
+func (Chain) GetConnectionID() (connectionID string)
+// GetClientStateHeight returns the client state height for the clientState corresponding to
+// the next chain in the channel path
+func (Chain) GetClientStateHeight() exported.Height
+// QueryStateAtHeight returns the value and proof of a key at the given height along with the height at
+// which the proof will succeed.
+func (Chain) QueryStateAtHeight(key string, height int64) (value []byte, proof []byte, height exported.Height)
+// UpdateClient updates the client state corresponding to the next chain in the channel path
+func (*Chain) UpdateClient()
+
+// ProofHeights contains multi-hop proof query height data.
+type ProofHeights struct {
+ proofHeight exported.Height // query the proof at this height
+ consensusHeight exported.Height // the proof is for the consensusState at this height
+}
+
+// ProofData is a generic proof struct.
+type ProofData struct {
+ Key *MerklePath
+ Value []byte
+ Proof []byte
+}
+
+// MultihopProof defines a set of proofs to verify a multihop message.
+// Consensus and Connection proofs are ordered from receiving to sending chain but do not include
+// the chain[1] consensus/connection state on chain[0] since it is already known on the receiving chain.
+type MultihopProof struct {
+ KeyProof *ProofData // the key/value proof on the on chain[KeyProofIndex] in the channel path
+ ConsensusProofs []*ProofData // array of consensus proofs starting with proof of consensusState of chain[1] on chain[2]
+ ConnectionProofs []*ProofData // array of connection proofs starting with proof of conn[1,2] on chain[2]
+}
+
+// QueryMultihopProof generates proof of a key/value at the proofHeight on indexed chain (chain0).
+// Chains are provided in order from the sending (source) chain to the receiving (verifying) chain.
+func QueryMultihopProof(
+ chains []*Chain,
+ key string,
+ keyHeight exported.Height,
+ includeKeyValue bool,
+) (
+ multihopProof MultihopProof,
+ multihopProofHeight exported.Height,
+) {
+
+ abortTransactionUnless(len(chains) > 1)
+
+ // calculate proof heights along channel path
+ proofHeights := make([]*ProofHeights, len(chains)-1)
+ abortTransactionUnless(calcProofHeights(chains, 1, keyHeight, proofHeights))
+
+ // the consensus state height of the proving chain's counterparty
+ // this is where multi-hop proof verification begins
+ multihopProofHeight = abortTransactionUnless(proofHeights[len(proofHeights)-1].consensusHeight.Decrement())
+
+ // the key/value proof height is the height of the consensusState on the source chain
+ keyHeight = abortTransactionUnless(proofHeights[0].consensusHeight.Decrement())
+
+ var value []byte
+ bytes, keyProof := chains[0].QueryStateAtHeight(key, keyHeight)
+
+ if includeKeyValue {
+ value = bytes
+ }
+
+ // assign the key/value proof
+ multihopProof.KeyProof = &ProofData{
+ Key: nil, // key to prove constructed during verification
+ Value: value, // proven values are constructed during verification (except for frozen client proofs)
+ Proof: keyProof,
+ }
+
+ // query proofs of consensus/connection states on intermediate chains
+ multihopProof.ConsensusProofs = make([]*ProofData, len(chains)-2)
+ multihopProof.ConnectionProofs = make([]*ProofData, len(chains)-2)
+ multihopProof.ConsensusProofs, multihopProof.ConnectionProofs = abortTransactionUnless(
+ queryIntermediateProofs(
+ chains,
+ len(chains)-2,
+ proofHeights,
+ multihopProof.ConsensusProofs,
+ multihopProof.ConnectionProofs)
+ )
+
+ return
+}
+
+// calcProofHeights calculates the optimal proof heights to generate a multi-hop proof
+// along the channel path and performs client updates as needed.
+func calcProofHeights(
+ chains []*Chain,
+ chainIdx int,
+ consensusHeight exported.Height,
+ proofHeights []*ProofHeights,
+) {
+ var height ProofHeights
+ chain := chains[chainIdx]
+
+ // find minimum consensus height provable on the next chain
+ // i.e. proofHeight is the minimum height at which the consensusState with
+ // height=consensusHeight can be proved on the chain (aka processedHeight)
+ height.proofHeight, height.consensusHeight = abortTransactionUnless(queryMinimumConsensusHeight(chain, consensusHeight, nil))
+
+ // if no suitable consensusHeight then update client and use latest chain height/client height
+ //
+ // TODO: It could be more efficient to update the client with the missing block height
+ // rather than the latest block height since it would be less likely to need client updates
+ // on subsequent chains.
+ if height.proofHeight == nil {
+ abortTransactionUnless(chain.UpdateClient())
+ height.proofHeight = chain.GetLatestHeight()
+ height.consensusHeight = chain.GetClientStateHeight(chains[chainIdx+1])
+ }
+
+ // stop on the next to last chain
+ if chainIdx == len(chains)-2 {
+ proofHeights[chainIdx-1] = &height
+ return
+ }
+
+ // use the proofHeight as the next consensus height
+ abortTransactionUnless(calcProofHeights(chains, chainIdx+1, height.proofHeight, proofHeights))
+
+ proofHeights[chainIdx-1] = &height
+ return
+}
+
+// queryIntermediateProofs recursively queries intermediate chains in a multi-hop channel path for consensus state
+// and connection proofs. It stops at the second to last path since the consensus and connection state on the
+// final hop is already known on the destination.
+func queryIntermediateProofs(
+ chains []*Chain,
+ proofIdx int,
+ proofHeights []*ProofHeights,
+ consensusProofs []*ProofData,
+ connectionProofs []*ProofData,
+) {
+ // no need to query proofs on final chain since the clientState is already known
+ if proofIdx < 0 {
+ return
+ }
+
+ chain := chains[proofIdx]
+ ph := proofHeights[proofIdx]
+
+ // query proof of the consensusState
+ proof := abortTransactionUnless(queryConsensusStateProof(chain, ph.proofHeight, ph.consensusHeight))
+ consensusProofs[len(p.Paths)-proofIdx-2] = proof
+
+ // query proof of the connectionEnd
+ proof = abortTransactionUnless(queryConnectionProof(chain, ph.proofHeight))
+ connectionProofs[len(p.Paths)-proofIdx-2] = proof
+
+ // continue querying proofs on the next chain in the path
+ queryIntermediateProofs(chains, proofIdx-1, proofHeights, consensusProofs, connectionProofs)
+}
+
+// Query a proof for the counterparty consensus state at the specified height on the given chain.
+func queryConsensusStateProof(
+ chain Chain,
+ proofHeight exported.Height,
+ consensusHeight exported.Height,
+) *ProofData {
+
+ key := host.FullConsensusStateKey(chain.GetClientID(), consensusHeight)
+ consensusStateBytes, consensusStateProof := chain.QueryStateAtHeight(key, int64(proofHeight.GetRevisionHeight()))
+ merklePath := abortTransactionUnless(chain.GetMerklePath(string(key)))
+
+ return &ProofData{
+ Key: merklePath,
+ Value: consensusStateBytes,
+ Proof: consensusStateProof,
+ }
+}
+
+// Query a proof for the connEnd on the given chain at the specified height.
+func queryConnectionProof(
+ chain Chain,
+ proofHeight exported.Height,
+) *ProofData {
+
+ key := host.ConnectionKey(chain.GetConnectionID())
+ connectionEndBytes, connectionEndProof := chain.QueryStateAtHeight(key, int64(proofHeight.GetRevisionHeight()))
+ merklePath := abortTransactionUnless(chain.GetMerklePath(string(key)))
+
+ return &ProofData{
+ Key: merklePath,
+ Value: connectionEndBytes,
+ Proof: connectionEndProof,
+ }
+}
+
+// queryMinimumConsensusHeight returns the minimum height within the provided
+// range at which a valid consensusState exists (processedHeight) and the
+// corresponding consensus state height (consensusHeight).
+func queryMinimumConsensusHeight(
+ chain Chain,
+ minConsensusHeight exported.Height,
+ limit uint64,
+) (
+ processedHeight exported.Height,
+ consensusHeight exported.Height
+) {
+
+ // find the minimum height consensus state
+ consensusHeight := minConsensusHeight
+ for i := uint64(0); i < limit; i++ {
+ key := host.FullClientKey(clientID, ibctm.ProcessedHeightKey(consensusHeight))
+ consensusStateHeightBytes, _ := abortTransactionUnless(chain.QueryStateAtHeight(key, chain.LastHeader.Header.Height, false))
+
+ if consensusStateHeightBytes != nil {
+ proofHeight := abortTransactionUnless(clienttypes.ParseHeight(string(consensusStateHeightBytes)))
+ return proofHeight, consensusHeight
+ }
+ consensusHeight = consensusHeight.Increment()
+ }
+
+ return nil, nil
+}
+```
+
+### Multi-hop Proof Verification Steps
+
+The following outlines the general proof verification steps specific to a multi-hop IBC message.
+
+1. Unpack the multihop proof bytes into consensus states, connection states and channel/commitment proof data.
+2. Check the counterparty client on the receiving end is active and the client height is greater than or equal to the proof height.
+3. Iterate through the connections states to determine the maximum `delayPeriod` for the channel path and verify that the counterparty consensus state on the receiving chain satisfies the delay requirement.
+4. Iterate through connection state proofs and verify each connectionEnd is in the OPEN state and check that the connection ids match the channel connectionHops.
+5. Verify the intermediate state proofs. Starting with known `ConsensusState[0]` at the given `proofHeight` on `Chain[1]` prove the prior chain's consensus and connection state.
+6. Verify that the client id in each consensus state proof key matches the client id in the ConnectionEnd in the previous connection state proof.
+7. Repeat step 5, proving `ConsensusState[i]`, and `Conn[i,i-1]` where `i` is the proof index starting with the consensus state on `Chain[2]`. `ConsensusState[1]` is already known on `Chain[0]`. Note that chains are indexed from executing (verifying) chain to and proofs are indexed in the opposite direction to match the connectionHops ordering.
+ - Verify ParseClientID(ConsensusProofs[i].Key) == ConnectionEnd.ClientID
+ - ConsensusProofs[i].Proof.VerifyMembership(ConsensusState.GetRoot(), ConsensusProofs[i].Key, ConsensusProofs[i].Value)
+ - ConnectionProofs[i].Proof.VerifyMembership(ConsensusState.GetRoot(), ConnectionProofs[i].Key, ConnectionProofs[i].Value)
+ - ConsensusState = ConsensusProofs[i].Value
+ - i++
+8. Finally, prove the expected channel or packet commitment in `ConsensusState[N-2]` (sending chain consensus state) on `Chain[1]`
+
+For more details see [ICS4](https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics).
+
+### Multi-hop Proof Verification Pseudo Code
+
+Pseudocode proof generation for a channel between `N` chains `C[N] --> C[i] --> C[0]`
+
+```go
+// Parse a client or connection ID from the connection proof key and return it.
+func parseID(prefixedKey *PrefixedKey) string {
+ keyPath := prefixedKey.KeyPath
+ abortTransactionUnless(len(keyPath) >= 2)
+ parts := strings.Split(keyPath[1], "/")
+ abortTransactionUnless(len(parts) >= 2)
+ return parts[1]
+}
+
+func parseClientID(prefixedKey *PrefixedKey) string {
+ return parseID(prefixedKey)
+}
+
+func parseConnectionID(prefixedKey *PrefixedKey) string {
+ return parseID(prefixedKey)
+}
+
+// VerifyMultihopMembership verifies a multihop membership proof.
+// Inputs: consensusState - The consensusState for chain[N-1], which is known on the receiving chain (chain[N]).
+// connectionHops - The expected connectionHops for the channel from the receiving chain to the sending chain.
+// proof - The serialized multihop proof data.
+// prefix - Merkleprefix to be combined with key to generate Merklepath for the key/value proof verification.
+// key - The key to prove in the indexed consensus state.
+// value - The value to prove in the indexed consensus state.
+func VerifyMultihopMembership(
+ consensusState exported.ConsensusState,
+ connectionHops []string,
+ proof MultihopProof,
+ prefix exported.Prefix,
+ key string,
+ value []byte,
+) {
+ // deserialize proof bytes into multihop proofs
+ proofs := abortTransactionUnless(Unmarshal(proof))
+ abortTransactionUnless(len(proofs.ConsensusProofs) >= 1)
+ abortTransactionUnless(len(proofs.ConnectionProofs) == len(proofs.ConsensusProofs))
+
+ // verify connection hop ordering and connections are in OPEN state
+ abortTransactionUnless(VerifyConnectionHops(proofs.ConnectionProofs, connectionHops))
+
+ // verify intermediate consensus and connection states from receiver --> sender
+ abortTransactionUnless(VerifyConsensusAndConnectionStates(consensusState, proofs.ConsensusProofs, proofs.ConnectionProofs))
+
+ // verify a key/value proof on source chain's consensus state.
+ abortTransactionUnless(VerifyKeyMembership(consensusState, proofs, prefix, key, value))
+}
+
+// VerifyMultihopNonMembership verifies a multihop non-membership proof.
+// Inputs: consensusState - The consensusState for chain[1], which is known on the receiving chain (chain[0]).
+// connectionHops - The expected connectionHops for the channel from the receiving chain to the sending chain.
+// proof - The serialized multihop proof data.
+// prefix - Merkleprefix to be combined with key to generate Merklepath for the key/value proof verification.
+// key - The key to prove absent in the indexed consensus state
+func VerifyMultihopNonMembership(
+ consensusState exported.ConsensusState,
+ connectionHops []string,
+ proof MultihopProof,
+ prefix exported.Prefix,
+ key string,
+) {
+ // deserialize proof bytes into multihop proofs
+ proofs := abortTransactionUnless(Unmarshal(proof))
+ abortTransactionUnless(len(proofs.ConsensusProofs) >= 1)
+ abortTransactionUnless(len(proofs.ConnectionProofs) == len(proofs.ConsensusProofs))
+
+ // verify connection hop ordering and connections are in OPEN state
+ abortTransactionUnless(VerifyConnectionHops(proofs.ConnectionProofs, connectionHops))
+
+ // verify intermediate consensus and connection states from receiver --> sender
+ abortTransactionUnless(VerifyIntermediateStateProofs(consensusState, proofs.ConsensusProofs, proofs.ConnectionProofs))
+
+ // verify a key/value proof on source chain's consensus state.
+ abortTransactionUnless(VerifyKeyNonMembership(consensusState, proofs, prefix, key))
+}
+
+// VerifyConnectionHops checks that each connection in the multihop proof is OPEN and matches the connections in connectionHops.
+func VerifyConnectionHops(
+ connectionProofs []*ProofData,
+ connectionHops []string,
+) {
+ abortTransactionUnless(len(connectionProofs) == len(connectionHops)-1)
+
+ // check all connections are in OPEN state and that the connection IDs match and are in the right order
+ for i, connData := range connectionProofs {
+ connectionEnd := abortTransactionUnless(Unmarshal(connData.Value))
+
+ // Verify the rest of the connectionHops (first hop already verified)
+ // 1. check the connectionHop values match the proofs and are in the same order.
+ connectionID := parseConnectionID(connData.Key)
+ abortTransactionUnless(connectionID == connectionHops[i+1])
+
+ // 2. check that the connectionEnd's are in the OPEN state.
+ abortTransactionUnless(connectionEnd.GetState() == int32(connectiontypes.OPEN))
+ }
+}
+
+// VerifyIntermediateStateProofs verifies the state of each intermediate consensus and connection state
+// starting from the receiving chain and finally proving the sending chain consensus and connection state.
+func VerifyIntermediateStateProofs(
+ consensusState exported.ConsensusState,
+ consensusProofs []*ProofData,
+ connectionProofs []*ProofData,
+) {
+ // iterate through proofs to prove from executing chain (receiver) to counterparty chain (sender)
+ var connection ConnectionEnd
+ for i := 0; i < len(consensusProofs); i++ {
+ consensusProof := abortTransactionUnless(Unmarshal(consensusProofs[i].Proof))
+ connectionProof := abortTransactionUnless(Unmarshal(connectionProofs[i].Proof))
+
+ // convert to tendermint consensus state
+ cs := abortTransactionUnless(consensusState.(*tmclient.ConsensusState))
+
+ // the client id in the consensusState key path should match the clientID for the next connectionEnd
+ expectedClientID := parseClientIDFromKey(consensusProof.PrefixedKey.KeyPath)
+
+ abortTransactionUnless(VerifyClientID(clientID, consensusProofs[i].Key))
+
+ // prove the consensus state of chain[i] on chain[i-1]
+
+ abortTransactionUnless(consensusProof.VerifyMembership(
+ commitmenttypes.GetSDKSpecs(),
+ cs.GetRoot(),
+ *consensusProof.Key,
+ consensusProof.Value,
+ ))
+
+ // prove the connection state of chain[i] on chain[i-1]
+ abortTransactionUnless(connectionProof.VerifyMembership(
+ commitmenttypes.GetSDKSpecs(),
+ cs.GetRoot(),
+ *connectionProof.Key,
+ connectionProof.Value,
+ ))
+
+ // verify that client id in the consensus state path matches the clientID in the connection end
+ abortTransactionUnless(Unmarshal(connectionProof.Value, &connection))
+ abortTransactionUnless(connection.ClientId == expectedClientID)
+
+ // update the consensusState to prove the next consensus/connection states
+ abortTransactionUnless(UnmarshalInterface(consensusProof.Value, &consensusState))
+ }
+}
+
+// VerifyKeyMembership verifies a key in the indexed chain consensus state.
+func VerifyKeyMembership(
+ consensusState exported.ConsensusState,
+ proofs *MultihopProof,
+ prefix exported.Prefix,
+ key string,
+ value []byte,
+) {
+ // create prefixed key for proof verification
+ prefixedKey := abortTransactionUnless(commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(key)))
+
+ // reassign consensus state for the final consensus proof if needed
+ if len(proofs) > 0 {
+ index := uint32(len(proofs.ConsensusProofs)) - 1
+ consensusState = abortTransactionUnless(UnmarshalInterface(proofs.ConsensusProofs[index].Value))
+ }
+
+ // assign the key proof to verify on the source chain
+ keyProof := abortTransactionUnless(Unmarshal(proofs.KeyProof.Proof))
+
+ abortTransactionUnless(keyProof.VerifyMembership(
+ commitmenttypes.GetSDKSpecs(),
+ consensusState.GetRoot(),
+ prefixedKey,
+ value,
+ ))
+
+}
+
+// VerifyKeyNonMembership verifies a key in the indexed chain consensus state.
+func VerifyKeyNonMembership(
+ consensusState exported.ConsensusState,
+ proofs *MsgMultihopProof,
+ prefix exported.Prefix,
+ key string,
+) {
+ // create prefixed key for proof verification
+ prefixedKey := abortTransactionUnless(commitmenttypes.ApplyPrefix(prefix, commitmenttypes.NewMerklePath(key)))
+
+ // reassign consensus state for the final consensus proof if needed
+ if len(proofs) > 0 {
+ index := uint32(len(proofs.ConsensusProofs)) - 1
+ consensusState = abortTransactionUnless(UnmarshalInterface(proofs.ConsensusProofs[index].Value))
+ }
+
+ // assign the key proof to verify on the source chain
+ keyProof := abortTransactionUnless(Unmarshal(proofs.KeyProof.Proof))
+
+ abortTransactionUnless(keyProof.VerifyNonMembership(
+ commitmenttypes.GetSDKSpecs(),
+ consensusState.GetRoot(),
+ prefixedKey,
+ ))
+}
+```
+
+### Path Forgery Protection
+
+From the view of a single network, a list of connection IDs describes an unforgeable path of pre-existing connections to a receiving chain. This is ensured by atomically incrementing connection IDs.
+
+We must verify a proof that the connection ID of each hop matches the proven connection state provided to the verifier. Additionally we must link the connection state to the consensus state for that hop as well. We are essentially proving out the connection path of the channel.
+
+## Backwards Compatibility
+
+The existing IBC message sending should not be affected. Minor changes to the verification logic would be required to identify a multi-hop packet and apply the proper proof verification logic. Multi-hop packets should be easily identified by the existence of more than one connection ID in the connectionHops field.
+
+## Forwards Compatibility
+
+If a decentralized chain name service is developed for the data currently in the chain registry on github, it may be possible for a calling app to specify only a unique chain name rather than an explicit set of connectionHops. The chain name service would maintain a mapping of chain names to channel paths. The name service could push routing table updates to subscribed chains. This should require fewer writes since routing table updates should be fewer than packets.
+
+## Example Implementation
+
+Coming soon.
+
+## Other Implementations
+
+Coming soon.
+
+## History
+
+Dec 16, 2022 - Revisions to channel spec and proof verification
+
+Nov 11, 2022 - Initial draft
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/core/ics-033-multi-hop/proof_generation.png b/ibc/next/spec/core/ics-033-multi-hop/proof_generation.png
new file mode 100644
index 00000000..090716a1
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/proof_generation.png differ
diff --git a/ibc/next/spec/core/ics-033-multi-hop/proof_query.png b/ibc/next/spec/core/ics-033-multi-hop/proof_query.png
new file mode 100644
index 00000000..7070fa2e
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/proof_query.png differ
diff --git a/ibc/next/spec/core/ics-033-multi-hop/proof_query_algorithm.png b/ibc/next/spec/core/ics-033-multi-hop/proof_query_algorithm.png
new file mode 100644
index 00000000..110c9673
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/proof_query_algorithm.png differ
diff --git a/ibc/next/spec/core/ics-033-multi-hop/proof_verification.png b/ibc/next/spec/core/ics-033-multi-hop/proof_verification.png
new file mode 100644
index 00000000..3a57e003
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/proof_verification.png differ
diff --git a/ibc/next/spec/core/ics-033-multi-hop/relayer_calc_update_heights.png b/ibc/next/spec/core/ics-033-multi-hop/relayer_calc_update_heights.png
new file mode 100644
index 00000000..a289c954
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/relayer_calc_update_heights.png differ
diff --git a/ibc/next/spec/core/ics-033-multi-hop/relayer_query_proof_and_submit.png b/ibc/next/spec/core/ics-033-multi-hop/relayer_query_proof_and_submit.png
new file mode 100644
index 00000000..ba11443a
Binary files /dev/null and b/ibc/next/spec/core/ics-033-multi-hop/relayer_query_proof_and_submit.png differ
diff --git a/ibc/next/spec/ics-001-ics-standard/README.mdx b/ibc/next/spec/ics-001-ics-standard/README.mdx
new file mode 100644
index 00000000..590bb42d
--- /dev/null
+++ b/ibc/next/spec/ics-001-ics-standard/README.mdx
@@ -0,0 +1,186 @@
+---
+ics: 1
+title: ICS Specification Standard
+stage: draft
+category: meta
+kind: meta
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-02-12
+modified: 2019-08-25
+---
+
+## What is an ICS?
+
+An interchain standard (ICS) is a design document describing a particular protocol,
+standard, or feature expected to be of use to the Cosmos ecosystem.
+An ICS should list the desired properties of the standard, explain the design rationale, and
+provide a concise but comprehensive technical specification. The primary ICS author
+is responsible for pushing the proposal through the standardisation process, soliciting
+input and support from the community, and communicating with relevant stakeholders to
+ensure (social) consensus.
+
+The interchain standardisation process should be the primary vehicle for proposing
+ecosystem-wide protocols, changes, and features, and ICS documents should persist after
+consensus as a record of design decisions and an information repository for future implementers.
+
+Interchain standards should *not* be used for proposing changes to a particular blockchain
+(such as the Cosmos Hub), specifying implementation particulars (such as language-specific data structures),
+or debating governance proposals on existing Cosmos blockchains (although it is possible
+that individual blockchains in the Cosmos ecosystem may utilise their governance processes
+to approve or reject interchain standards).
+
+## Components
+
+An ICS consists of a header, synopsis, specification, history log, and copyright notice. All top-level sections are required.
+References should be included inline as links, or tabulated at the bottom of the section if necessary.
+
+### Header
+
+An ICS header contains metadata relevant to the ICS.
+
+#### Required fields
+
+`ics: #` - ICS number (assigned sequentially)
+
+`title` - ICS title (keep it short & sweet)
+
+`stage` - Current ICS stage, see [PROCESS.md](https://github.com/cosmos/ibc/blob/main/meta/PROCESS.md) for the list of possible stages.
+
+See [README.md](https://github.com/cosmos/ibc/blob/main/README.md) for a description of the ICS acceptance stages.
+
+`category` - ICS category, one of the following:
+
+- `meta` - A standard about the ICS process.
+- `IBC/TAO` - A standard about an inter-blockchain communication system core transport, authentication, and ordering layer protocol.
+- `IBC/APP` - A standard about an inter-blockchain communication system application layer protocol.
+
+`kind` - ICS kind, one of the following:
+
+- `meta` - A standard about the ICS process.
+- `interface` - A standard about the minimal set of interfaces that must be provided and properties that must be fulfilled by a state machine hosting an implementation of the inter-blockchain communication protocol.
+- `instantiation` - A standard about concrete implementation details that explains how the standard is realized in pseudocode or software components.
+
+`author` - ICS author(s) & contact information (in order of preference: email, GitHub handle, Twitter handle, other contact methods likely to elicit response).
+ The first author is the primary "owner" of the ICS and is responsible for advancing it through the standardisation process.
+ Subsequent author ordering should be in order of contribution amount.
+
+`created` - Date ICS was first created (`YYYY-MM-DD`)
+
+`modified` - Date ICS was last modified (`YYYY-MM-DD`)
+
+#### Optional fields
+
+`requires` - Other ICS standards, referenced by number, which are required or depended upon by this standard.
+
+`required-by` - Other ICS standards, referenced by number, which require or depend upon this standard.
+
+`replaces` - Another ICS standard replaced or supplanted by this standard, if applicable.
+
+`replaced-by` - Another ICS standard which replaces or supplants this standard, if applicable.
+
+`version compatibility` - List of versions of implementations compatible with the ICS standard.
+
+### Synopsis
+
+Following the header, an ICS should include a brief (~200 word) synopsis providing a high-level
+description of and rationale for the specification.
+
+### Specification
+
+The specification section is the main component of an ICS, and should contain protocol documentation, design rationale,
+required references, and technical details where appropriate.
+
+#### Sub-components
+
+The specification may have any or all of the following sub-components, as appropriate to the particular ICS. Included sub-components should be listed in the order specified here.
+
+- *Motivation* - A rationale for the existence of the proposed feature, or the proposed changes to an existing feature.
+- *Definitions* - A list of new terms or concepts utilised in this ICS or required to understand this ICS. Any terms not defined in the top-level "docs" folder must be defined here.
+- *Desired Properties* - A list of the desired properties or characteristics of the protocol or feature specified, and expected effects or failures when the properties are violated.
+- *Technical Specification* - All technical details of the proposed protocol including syntax, semantics, sub-protocols, data structures, algorithms, and pseudocode as appropriate.
+ The technical specification should be detailed enough such that separate correct implementations of the specification without knowledge of each other are compatible.
+- *Backwards Compatibility* - A discussion of compatibility (or lack thereof) with previous feature or protocol versions.
+- *Forwards Compatibility* - A discussion of compatibility (or lack thereof) with future possible or expected features or protocol versions.
+- *Example Implementations* - Concrete example implementations or descriptions of expected implementations to serve as the primary reference for implementers.
+- *Other Implementations* - A list of candidate or finalised implementations (external references, not inline).
+
+### History
+
+An ICS should include a history section, listing any inspiring documents and a plaintext log of significant changes.
+
+See an example history section [below](#history-1).
+
+### Copyright
+
+An ICS should include a copyright section waiving rights via [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
+
+## Formatting
+
+### General
+
+ICS specifications must be written in GitHub-flavoured Markdown.
+
+For a GitHub-flavoured Markdown cheat sheet, see [here](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). For a local Markdown renderer, see [here](https://github.com/joeyespo/grip).
+
+### Language
+
+ICS specifications should be written in Simple English, avoiding obscure terminology and unnecessary jargon. For excellent examples of Simple English, please see the [Simple English Wikipedia](https://simple.wikipedia.org/wiki/Main_Page).
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in specifications are to be interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
+
+### Pseudocode
+
+Pseudocode in specifications should be language-agnostic and formatted in a simple imperative standard, with line numbers, variables, simple conditional blocks, for loops, and
+English fragments where necessary to explain further functionality such as scheduling timeouts. LaTeX images should be avoided because they are difficult to review in diff form.
+
+Pseudocode for structs should be written in simple Typescript, as interfaces.
+
+Example pseudocode struct:
+
+```typescript
+interface Connection {
+ state: ConnectionState
+ version: Version
+ counterpartyIdentifier: Identifier
+ consensusState: ConsensusState
+}
+```
+
+Pseudocode for algorithms should be written in simple Typescript, as functions.
+
+Example pseudocode algorithm:
+
+```typescript
+function startRound(round) {
+ round_p = round
+ step_p = PROPOSE
+ if (proposer(h_p, round_p) === p) {
+ if (validValue_p !== nil)
+ proposal = validValue_p
+ else
+ proposal = getValue()
+ broadcast( {PROPOSAL, h_p, round_p, proposal, validRound} )
+ } else
+ schedule(onTimeoutPropose(h_p, round_p), timeoutPropose(round_p))
+}
+```
+
+## History
+
+This specification was significantly inspired by and derived from Ethereum's [EIP 1](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1.md), which
+was in turn derived from Bitcoin's BIP process and Python's PEP process. Antecedent authors are not responsible for any shortcomings of this ICS spec or
+the ICS process. Please direct all comments to the ICS repository maintainers.
+
+Mar 4, 2019 - Initial draft finished and submitted as a PR
+
+Mar 7, 2019 - Draft merged
+
+Apr 11, 2019 - Updates to pseudocode formatting, add definitions subsection
+
+Aug 17, 2019 - Clarifications to categories
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/ics-template.md b/ibc/next/spec/ics-template.md
new file mode 100644
index 00000000..2b3f6096
--- /dev/null
+++ b/ibc/next/spec/ics-template.md
@@ -0,0 +1,68 @@
+---
+ics: (number)
+title: (short title)
+stage: (current process stage)
+category: (ics category)
+kind: (ics kind)
+author: (primary & additional authors)
+created: (creation date)
+modified: (modification date)
+requires: (optional list of ics numbers)
+required-by: (optional list of ics numbers)
+implements: (optional list of ics numbers)
+version compatibility: (optional list of compatible implementations' releases)
+---
+
+## Synopsis
+
+(high-level description of and rationale for specification)
+
+### Motivation
+
+(rationale for existence of standard)
+
+### Definitions
+
+(definitions of any new terms not defined in common documentation)
+
+### Desired Properties
+
+(desired characteristics / properties of protocol, effects if properties are violated)
+
+## Technical Specification
+
+(main part of standard document - not all subsections are required)
+
+(detailed technical specification: syntax, semantics, sub-protocols, algorithms, data structures, etc)
+
+### Data Structures
+
+(new data structures, if applicable)
+
+### Sub-protocols
+
+(sub-protocols, if applicable)
+
+### Properties & Invariants
+
+(properties & invariants maintained by the protocols specified, if applicable)
+
+## Backwards Compatibility
+
+(discussion of compatibility or lack thereof with previous standards)
+
+## Forwards Compatibility
+
+(discussion of compatibility or lack thereof with expected future standards)
+
+## Example Implementations
+
+(links to or descriptions of concrete example implementations)
+
+## History
+
+(changelog and notable inspirations / references)
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
diff --git a/ibc/next/spec/relayer/ics-018-relayer-algorithms/README.mdx b/ibc/next/spec/relayer/ics-018-relayer-algorithms/README.mdx
new file mode 100644
index 00000000..7dc4e7f5
--- /dev/null
+++ b/ibc/next/spec/relayer/ics-018-relayer-algorithms/README.mdx
@@ -0,0 +1,294 @@
+---
+ics: 18
+title: Relayer Algorithms
+stage: draft
+category: IBC/TAO
+kind: interface
+requires: [24, 25, 26]
+authors:
+ - name: Christopher Goes
+ email: cwgoes@tendermint.com
+created: 2019-03-07
+modified: 2019-08-25
+---
+
+## Synopsis
+
+Relayer algorithms are the "physical" connection layer of IBC — off-chain processes responsible for relaying data between two chains running the IBC protocol by scanning the state of each chain, constructing appropriate datagrams, and executing them on the opposite chain as allowed by the protocol.
+
+### Motivation
+
+In the IBC protocol, a blockchain can only record the *intention* to send particular data to another chain — it does not have direct access to a network transport layer. Physical datagram relay must be performed by off-chain infrastructure with access to a transport layer such as TCP/IP. This standard defines the concept of a *relayer* algorithm, executable by an off-chain process with the ability to query chain state, to perform this relay.
+
+### Definitions
+
+A *relayer* is an off-chain process with the ability to read the state of and submit transactions to some set of ledgers utilising the IBC protocol.
+
+### Desired Properties
+
+- No exactly-once or deliver-or-timeout safety properties of IBC should depend on relayer behaviour (assume Byzantine relayers).
+- Packet relay liveness properties of IBC should depend only on the existence of at least one correct, live relayer.
+- Relaying should be permissionless, all requisite verification should be performed on-chain.
+- Requisite communication between the IBC user and the relayer should be minimised.
+- Provision for relayer incentivisation should be possible at the application layer.
+
+## Technical Specification
+
+### Basic relayer algorithm
+
+The relayer algorithm is defined over a set `C` of chains implementing the IBC protocol. Each relayer may not necessarily have access to read state from and write datagrams to all chains in the interchain network (especially in the case of permissioned or private chains) — different relayers may relay between different subsets.
+
+`pendingDatagrams` calculates the set of all valid datagrams to be relayed from one chain to another based on the state of both chains. The relayer must possess prior knowledge of what subset of the IBC protocol is implemented by the blockchains in the set for which they are relaying (e.g. by reading the source code). An example is defined below.
+
+`submitDatagram` is a procedure defined per-chain (submitting a transaction of some sort). Datagrams can be submitted individually as single transactions or atomically as a single transaction if the chain supports it.
+
+`relay` is called by the relayer every so often — no more frequently than once per block on either chain, and possibly less frequently, according to how often the relayer wishes to relay.
+
+Different relayers may relay between different chains — as long as each pair of chains has at least one correct & live relayer and the chains remain live, all packets flowing between chains in the network will eventually be relayed.
+
+```typescript
+function relay(C: Set) {
+ for (const chain of C)
+ for (const counterparty of C)
+ if (counterparty !== chain) {
+ const datagrams = chain.pendingDatagrams(counterparty)
+ for (const localDatagram of datagrams[0])
+ chain.submitDatagram(localDatagram)
+ for (const counterpartyDatagram of datagrams[1])
+ counterparty.submitDatagram(counterpartyDatagram)
+ }
+}
+```
+
+### Packets, acknowledgements, timeouts
+
+#### Relaying packets in an ordered channel
+
+Packets in an ordered channel can be relayed in either an event-based fashion or a query-based fashion.
+For the former, the relayer should watch the source chain for events emitted whenever packets are sent,
+then compose the packet using the data in the event log. For the latter, the relayer should periodically
+query the send sequence on the source chain, and keep the last sequence number relayed, so that any sequences
+in between the two are packets that need to be queried & then relayed. In either case, subsequently, the relayer process
+should check that the destination chain has not yet received the packet by checking the receive sequence, and then relay it.
+
+#### Relaying packets in an unordered channel
+
+Packets in an unordered channel can be relayed in an event-based fashion.
+The relayer should watch the source chain for events emitted whenever packets
+are sent, then compose the packet using the data in the event log. Subsequently,
+the relayer should check whether the destination chain has received the packet
+already by querying for the presence of an acknowledgement at the packet's sequence
+number, and if one is not yet present the relayer should relay the packet.
+
+#### Relaying acknowledgements
+
+Acknowledgements can be relayed in an event-based fashion. The relayer should
+watch the destination chain for events emitted whenever packets are received & acknowledgements
+are written, then compose the acknowledgement using the data in the event log,
+check whether the packet commitment still exists on the source chain (it will be
+deleted once the acknowledgement is relayed), and if so relay the acknowledgement to
+the source chain.
+
+#### Relaying timeouts (ordinary case, no TIMEOUT receipt)
+
+Timeout relay is slightly more complex since there is no specific event emitted when
+a packet times-out - it is simply the case that the packet can no longer be relayed,
+since the timeout height or timestamp has passed on the destination chain. The relayer
+process must elect to track a set of packets (which can be constructed by scanning event logs),
+and as soon as the height or timestamp of the destination chain exceeds that of a tracked
+packet, check whether the packet commitment still exists on the source chain (it will
+be deleted once the timeout is relayed), and if so relay a timeout to the source chain.
+
+#### Relaying timeouts for channels that write TIMEOUT receipts
+
+Some channel types (e.g. ORDERED_ALLOW_TIMEOUT), can only timeout a packet if a timeout receipt
+is written on the destination chain. This requires a relayer to first attempt a receive on the destination chain
+even if the packet is already timed out, before they can relay a timeout to the sending chain. Thus on these channels,
+relayers must check if packet has already been received on the destination chain by querying the packet receipt path.
+If a value does not already exist, then attempt to receive the packet on the destination chain. If a timeout receipt
+is written, then relay the timeout with a proof of the timeout receipt back to the sender chain.
+
+### Pending datagrams
+
+`pendingDatagrams` collates datagrams to be sent from one machine to another. The implementation of this function will depend on the subset of the IBC protocol supported by both machines & the state layout of the source machine. Particular relayers will likely also want to implement their own filter functions in order to relay only a subset of the datagrams which could possibly be relayed (e.g. the subset for which they have been paid to relay in some off-chain manner).
+
+An example implementation which performs unidirectional relay between two chains is outlined below. It can be altered to perform bidirectional relay by switching `chain` and `counterparty`.
+Which relayer process is responsible for which datagrams is a flexible choice - in this example, the relayer process relays all handshakes which started on `chain` (sending datagrams to both chains), relays all packets sent from `chain` to `counterparty`, and relays all acknowledgements of packets sent from `counterparty` to `chain`.
+
+```typescript
+function pendingDatagrams(chain: Chain, counterparty: Chain): List> {
+ const localDatagrams = []
+ const counterpartyDatagrams = []
+
+ // ICS2 : Clients
+ // - Determine if light client needs to be updated (local & counterparty)
+ height = chain.latestHeight()
+ client = counterparty.queryClientConsensusState(chain)
+ if client.height < height {
+ header = chain.latestHeader()
+ counterpartyDatagrams.push(ClientUpdate{chain, header})
+ }
+ counterpartyHeight = counterparty.latestHeight()
+ client = chain.queryClientConsensusState(counterparty)
+ if client.height < counterpartyHeight {
+ header = counterparty.latestHeader()
+ localDatagrams.push(ClientUpdate{counterparty, header})
+ }
+
+ // ICS3 : Connections
+ // - Determine if any connection handshakes are in progress
+ connections = chain.getConnectionsUsingClient(counterparty)
+ for (const localEnd of connections) {
+ remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier)
+ if (localEnd.state === INIT && remoteEnd === null)
+ // Handshake has started locally (1 step done), relay `connOpenTry` to the remote end
+ counterpartyDatagrams.push(ConnOpenTry{
+ desiredIdentifier: localEnd.counterpartyConnectionIdentifier,
+ counterpartyConnectionIdentifier: localEnd.identifier,
+ counterpartyClientIdentifier: localEnd.clientIdentifier,
+ counterpartyPrefix: localEnd.commitmentPrefix,
+ clientIdentifier: localEnd.counterpartyClientIdentifier,
+ version: localEnd.version,
+ counterpartyVersion: localEnd.version,
+ proofInit: localEnd.proof(height),
+ proofConsensus: localEnd.client.consensusState.proof(),
+ proofHeight: height,
+ consensusHeight: localEnd.client.height,
+ })
+ else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN)
+ // Handshake has started on the other end (2 steps done), relay `connOpenAck` to the local end
+ localDatagrams.push(ConnOpenAck{
+ identifier: localEnd.identifier,
+ version: remoteEnd.version,
+ proofTry: remoteEnd.proof(counterpartyHeight),
+ proofConsensus: remoteEnd.client.consensusState.proof(),
+ proofHeight: counterpartyHeight,
+ consensusHeight: remoteEnd.client.height,
+ })
+ else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN)
+ // Handshake has confirmed locally (3 steps done), relay `connOpenConfirm` to the remote end
+ counterpartyDatagrams.push(ConnOpenConfirm{
+ identifier: remoteEnd.identifier,
+ proofAck: localEnd.proof(height),
+ proofHeight: height,
+ })
+ }
+
+ // ICS4 : Channels & Packets
+ // - Determine if any channel handshakes are in progress
+ // - Determine if any packets, acknowledgements, or timeouts need to be relayed
+ channels = chain.getChannelsUsingConnections(connections)
+ for (const localEnd of channels) {
+ remoteEnd = counterparty.getConnection(localEnd.counterpartyIdentifier)
+ // Deal with handshakes in progress
+ if (localEnd.state === INIT && remoteEnd === null)
+ // Handshake has started locally (1 step done), relay `chanOpenTry` to the remote end
+ counterpartyDatagrams.push(ChanOpenTry{
+ order: localEnd.order,
+ connectionHops: localEnd.connectionHops.reverse(),
+ portIdentifier: localEnd.counterpartyPortIdentifier,
+ channelIdentifier: localEnd.counterpartyChannelIdentifier,
+ counterpartyPortIdentifier: localEnd.portIdentifier,
+ counterpartyChannelIdentifier: localEnd.channelIdentifier,
+ version: localEnd.version,
+ counterpartyVersion: localEnd.version,
+ proofInit: localEnd.proof(height),
+ proofHeight: height,
+ })
+ else if (localEnd.state === INIT && remoteEnd.state === TRYOPEN)
+ // Handshake has started on the other end (2 steps done), relay `chanOpenAck` to the local end
+ localDatagrams.push(ChanOpenAck{
+ portIdentifier: localEnd.portIdentifier,
+ channelIdentifier: localEnd.channelIdentifier,
+ version: remoteEnd.version,
+ proofTry: remoteEnd.proof(counterpartyHeight),
+ proofHeight: counterpartyHeight,
+ })
+ else if (localEnd.state === OPEN && remoteEnd.state === TRYOPEN)
+ // Handshake has confirmed locally (3 steps done), relay `chanOpenConfirm` to the remote end
+ counterpartyDatagrams.push(ChanOpenConfirm{
+ portIdentifier: remoteEnd.portIdentifier,
+ channelIdentifier: remoteEnd.channelIdentifier,
+ proofAck: localEnd.proof(height),
+ proofHeight: height
+ })
+
+ // Deal with packets
+ // First, scan logs for sent packets and relay all of them
+ sentPacketLogs = queryByTopic(height, "sendPacket")
+ for (const logEntry of sentPacketLogs) {
+ // relay packet with this sequence number
+ packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp,
+ localEnd.portIdentifier, localEnd.channelIdentifier,
+ remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data}
+ counterpartyDatagrams.push(PacketRecv{
+ packet: packetData,
+ proof: packet.proof(),
+ proofHeight: height,
+ })
+ }
+
+ // Then, scan logs for acknowledgements, relay back to sending chain
+ recvPacketLogs = queryByTopic(height, "writeAcknowledgement")
+ for (const logEntry of recvPacketLogs) {
+ // relay packet acknowledgement with this sequence number
+ packetData = Packet{logEntry.sequence, logEntry.timeoutHeight, logEntry.timeoutTimestamp,
+ localEnd.portIdentifier, localEnd.channelIdentifier,
+ remoteEnd.portIdentifier, remoteEnd.channelIdentifier, logEntry.data}
+ counterpartyDatagrams.push(PacketAcknowledgement{
+ packet: packetData,
+ acknowledgement: logEntry.acknowledgement,
+ proof: packet.proof(),
+ proofHeight: height,
+ })
+ }
+ }
+
+ return [localDatagrams, counterpartyDatagrams]
+}
+```
+
+Relayers may elect to filter these datagrams in order to relay particular clients, particular connections, particular channels, or even particular kinds of packets, perhaps in accordance with the fee payment model (which this document does not specify, as it may vary).
+
+### Ordering constraints
+
+There are implicit ordering constraints imposed on the relayer process determining which datagrams must be submitted in what order. For example, a header must be submitted to finalise the stored consensus state & commitment root for a particular height in a light client before a packet can be relayed. The relayer process is responsible for frequently querying the state of the chains between which they are relaying in order to determine what must be relayed when.
+
+### Bundling
+
+If the host state machine supports it, the relayer process can bundle many datagrams into a single transaction, which will cause them to be executed in sequence, and amortise any overhead costs (e.g. signature checks for fee payment).
+
+### Race conditions
+
+Multiple relayers relaying between the same pair of modules & chains may attempt to relay the same packet (or submit the same header) at the same time. If two relayers do so, the first transaction will succeed and the second will fail. Out-of-band coordination between the relayers or between the actors who sent the original packets and the relayers is necessary to mitigate this. Further discussion is out of scope of this standard.
+
+### Incentivisation
+
+The relay process must have access to accounts on both chains with sufficient balance to pay for transaction fees. Relayers may employ application-level methods to recoup these fees, such as by including a small payment to themselves in the packet data — protocols for relayer fee payment will be described in future versions of this ICS or in separate ICSs.
+
+Any number of relayer processes may be safely run in parallel (and indeed, it is expected that separate relayers will serve separate subsets of the interchain). However, they may consume unnecessary fees if they submit the same proof multiple times, so some minimal coordination may be ideal (such as assigning particular relayers to particular packets or scanning mempools for pending transactions).
+
+## Backwards Compatibility
+
+Not applicable. The relayer process is off-chain and can be upgraded or downgraded as necessary.
+
+## Forwards Compatibility
+
+Not applicable. The relayer process is off-chain and can be upgraded or downgraded as necessary.
+
+## Example Implementations
+
+- Implementation of ICS 18 in Go can be found in [cosmos/relayer repository](https://github.com/cosmos/relayer).
+- Implementation of ICS 18 in Rust can be found in [informalsystems/hermes repository](https://github.com/informalsystems/hermes).
+
+## History
+
+Mar 30, 2019 - Initial draft submitted
+
+Apr 15, 2019 - Revisions for formatting and clarity
+
+Apr 23, 2019 - Revisions from comments; draft merged
+
+## Copyright
+
+All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).