diff --git a/chains/sui/contracts/ccip/.gitignore b/chains/sui/contracts/ccip/.gitignore
new file mode 100644
index 0000000..813de75
--- /dev/null
+++ b/chains/sui/contracts/ccip/.gitignore
@@ -0,0 +1,4 @@
+build/*
+traces/*
+.trace
+.coverage*
diff --git a/chains/sui/contracts/ccip/Move.lock b/chains/sui/contracts/ccip/Move.lock
new file mode 100644
index 0000000..04681a5
--- /dev/null
+++ b/chains/sui/contracts/ccip/Move.lock
@@ -0,0 +1,56 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 3
+manifest_digest = "FD5E62CCB7099A0B4B6CADEFAB8BDCFF066D52DFAC950B11C7A47C4DE4BE4E79"
+deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C"
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "Bridge"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "d75fae3aa7fa3545b5803980a1e0c965b8bbf48e", subdir = "crates/sui-framework/packages/bridge" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "d75fae3aa7fa3545b5803980a1e0c965b8bbf48e", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+id = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "d75fae3aa7fa3545b5803980a1e0c965b8bbf48e", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+]
+
+[[move.package]]
+id = "SuiSystem"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "d75fae3aa7fa3545b5803980a1e0c965b8bbf48e", subdir = "crates/sui-framework/packages/sui-system" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.61.2"
+edition = "2024.beta"
+flavor = "sui"
+
+[env]
+
+[env.testnet]
+chain-id = "4c78adac"
+original-published-id = "0xe6763a05a2a7cb9250342d7420c566c6d4255920640c64c399beab47b6313e9b"
+latest-published-id = "0xe6763a05a2a7cb9250342d7420c566c6d4255920640c64c399beab47b6313e9b"
+published-version = "1"
diff --git a/chains/sui/contracts/ccip/Move.toml b/chains/sui/contracts/ccip/Move.toml
new file mode 100644
index 0000000..1421d9b
--- /dev/null
+++ b/chains/sui/contracts/ccip/Move.toml
@@ -0,0 +1,33 @@
+[package]
+name = "ChainlinkCCIP"
+edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
+# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
+# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]
+
+[dependencies]
+
+# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
+# Revision can be a branch, a tag, and a commit hash.
+# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
+
+# For local dependencies use `local = path`. Path is relative to the package root
+# Local = { local = "../path/to" }
+
+# To resolve a version conflict and force a specific version for dependency
+# override use `override = true`
+# Override = { local = "../conflicting/version", override = true }
+
+[addresses]
+ccip = "0x0"
+
+# Named addresses will be accessible in Move as `@name`. They're also exported:
+# for example, `std = "0x1"` is exported by the Standard Library.
+# alice = "0xA11CE"
+
+[dev-dependencies]
+# The dev-dependencies section allows overriding dependencies for `--test` and
+# `--dev` modes. You can introduce test-only dependencies here.
+# Local = { local = "../path/to/dev-build" }
+
+[dev-addresses]
+ccip = "0x3000"
diff --git a/chains/sui/contracts/ccip/sources/fee_quoter.move b/chains/sui/contracts/ccip/sources/fee_quoter.move
new file mode 100644
index 0000000..2afe8fb
--- /dev/null
+++ b/chains/sui/contracts/ccip/sources/fee_quoter.move
@@ -0,0 +1,42 @@
+module ccip::fee_quoter;
+
+use sui::clock;
+use sui::event;
+
+public struct UsdPerTokenUpdated has copy, drop {
+ token: address,
+ usd_per_token: u256,
+ timestamp: u64,
+}
+
+public struct UsdPerUnitGasUpdated has copy, drop {
+ dest_chain_selector: u64,
+ usd_per_unit_gas: u256,
+ timestamp: u64,
+}
+
+public fun emit_usd_per_token_updated(clock: &clock::Clock, token: address, usd_per_token: u256) {
+ event::emit(UsdPerTokenUpdated {
+ token,
+ usd_per_token,
+ timestamp: clock.timestamp_ms(),
+ })
+}
+
+public fun emit_usd_per_unit_gas_updated(clock: &clock::Clock, usd_per_unit_gas: u256) {
+ event::emit(UsdPerUnitGasUpdated {
+ dest_chain_selector: 17529533435026248318,
+ usd_per_unit_gas,
+ timestamp: clock.timestamp_ms(),
+ })
+}
+
+public fun drill_price_registries(
+ clock: &clock::Clock,
+ token: address,
+ usd_per_token: u256,
+ usd_per_unit_gas: u256,
+) {
+ emit_usd_per_token_updated(clock, token, usd_per_token);
+ emit_usd_per_unit_gas_updated(clock, usd_per_unit_gas);
+}
diff --git a/chains/sui/contracts/ccip/sources/state_object.move b/chains/sui/contracts/ccip/sources/state_object.move
new file mode 100644
index 0000000..4d653da
--- /dev/null
+++ b/chains/sui/contracts/ccip/sources/state_object.move
@@ -0,0 +1,65 @@
+module ccip::state_object;
+
+use std::ascii;
+use std::string::{Self, String};
+use std::type_name;
+use sui::address;
+use sui::derived_object;
+use sui::event;
+use sui::object;
+use sui::transfer;
+use sui::vec_map::{Self, VecMap};
+
+public fun type_and_version(): String {
+ string::utf8(b"StateObject 1.6.0")
+}
+
+public struct CCIPObject has key {
+ id: UID,
+}
+
+public struct CCIPObjectRef has key, store {
+ id: UID,
+ package_ids: vector
,
+}
+
+public struct CCIPObjectRefPointer has key, store {
+ id: UID,
+ ccip_object_id: address,
+}
+
+public struct STATE_OBJECT has drop {}
+
+fun init(otw: STATE_OBJECT, ctx: &mut TxContext) {
+ let mut ccip_object = CCIPObject { id: object::new(ctx) };
+
+ let mut ref = CCIPObjectRef {
+ id: derived_object::claim(&mut ccip_object.id, b"CCIPObjectRef"),
+ package_ids: vector[],
+ };
+
+ let pointer = CCIPObjectRefPointer {
+ id: object::new(ctx),
+ ccip_object_id: object::id_address(&ccip_object),
+ };
+
+ let tn = type_name::with_original_ids();
+ let package_bytes = ascii::into_bytes(tn.address_string());
+ let package_id = address::from_ascii_bytes(&package_bytes);
+ ref.package_ids.push_back(package_id);
+
+ transfer::share_object(ref);
+ transfer::share_object(ccip_object);
+
+ transfer::transfer(pointer, package_id);
+}
+
+public fun add_package_id(ref: &mut CCIPObjectRef, package_id: address) {
+ ref.package_ids.push_back(package_id);
+}
+
+public fun remove_package_id(ref: &mut CCIPObjectRef, package_id: address) {
+ let (found, idx) = ref.package_ids.index_of(&package_id);
+ assert!(found, 1);
+ ref.package_ids.swap_remove(idx);
+}
diff --git a/chains/sui/contracts/ccip/tests/ccip_tests.move b/chains/sui/contracts/ccip/tests/ccip_tests.move
new file mode 100644
index 0000000..d1c360e
--- /dev/null
+++ b/chains/sui/contracts/ccip/tests/ccip_tests.move
@@ -0,0 +1,18 @@
+/*
+#[test_only]
+module ccip::ccip_tests;
+// uncomment this line to import the module
+// use ccip::ccip;
+
+const ENotImplemented: u64 = 0;
+
+#[test]
+fun test_ccip() {
+ // pass
+}
+
+#[test, expected_failure(abort_code = ::ccip::ccip_tests::ENotImplemented)]
+fun test_ccip_fail() {
+ abort ENotImplemented
+}
+*/
diff --git a/chains/sui/contracts/ccip_offramp/.gitignore b/chains/sui/contracts/ccip_offramp/.gitignore
new file mode 100644
index 0000000..813de75
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/.gitignore
@@ -0,0 +1,4 @@
+build/*
+traces/*
+.trace
+.coverage*
diff --git a/chains/sui/contracts/ccip_offramp/Move.lock b/chains/sui/contracts/ccip_offramp/Move.lock
new file mode 100644
index 0000000..9d9f54d
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/Move.lock
@@ -0,0 +1,68 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 3
+manifest_digest = "29F173AA58C24C5E12AF017CDB866784A6A8EF5F39E0064C4BF85F6CA0AD0D0F"
+deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697"
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "ChainlinkCCIP", name = "ChainlinkCCIP" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "Bridge"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/bridge" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "ChainlinkCCIP"
+source = { local = "../ccip" }
+
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+id = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+]
+
+[[move.package]]
+id = "SuiSystem"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-system" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.61.2"
+edition = "2024.beta"
+flavor = "sui"
+
+[env]
+
+[env.testnet]
+chain-id = "4c78adac"
+original-published-id = "0x867da368e81618e8ee282085675f06e357caa52faf9421e6ba6a29438615d6cf"
+latest-published-id = "0x867da368e81618e8ee282085675f06e357caa52faf9421e6ba6a29438615d6cf"
+published-version = "1"
diff --git a/chains/sui/contracts/ccip_offramp/Move.toml b/chains/sui/contracts/ccip_offramp/Move.toml
new file mode 100644
index 0000000..6a7ff44
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/Move.toml
@@ -0,0 +1,16 @@
+[package]
+name = "CCIP_OFFRAMP"
+edition = "2024.beta"
+
+[dependencies]
+ChainlinkCCIP = { local = "../ccip" }
+
+[addresses]
+ccip_offramp = "0x0"
+ccip = "0xe6763a05a2a7cb9250342d7420c566c6d4255920640c64c399beab47b6313e9b"
+router = "0x0d1cefe6e43cc96cbb6b2e7aa261b8be05cbf926a8f83431e1bd6023250b522c"
+onramp = "0xefd74f2ab89903cd0a6783af2937c8cb932812a41590d7842d632dd9770b3e68"
+
+[dev-addresses]
+ccip_offramp = "0x1000"
+ccip = "0x3000"
diff --git a/chains/sui/contracts/ccip_offramp/sources/ocr3_base.move b/chains/sui/contracts/ccip_offramp/sources/ocr3_base.move
new file mode 100644
index 0000000..1c17301
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/sources/ocr3_base.move
@@ -0,0 +1,25 @@
+module ccip_offramp::ocr3_base;
+
+use sui::event;
+
+public struct ConfigSet has copy, drop {
+ ocr_plugin_type: u8,
+ config_digest: vector,
+ signers: vector>,
+ transmitters: vector,
+ big_f: u8,
+}
+
+public fun emit_config_set() {
+ event::emit(ConfigSet {
+ ocr_plugin_type: 1,
+ config_digest: vector[],
+ signers: vector[],
+ transmitters: vector[
+ @0x7f8e09ea5a9d6e72854af8d6fa3e4d6f5466d9c11b498e0d9d4f64d8cabe8d32,
+ @0xc8cc7f57a25e6e96e6be7bcd606a1a8d59d85d757d08e7e045e87a8cfe0bcbc7,
+ @0x5,
+ ],
+ big_f: 2,
+ });
+}
diff --git a/chains/sui/contracts/ccip_offramp/sources/offramp.move b/chains/sui/contracts/ccip_offramp/sources/offramp.move
new file mode 100644
index 0000000..5cbbd99
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/sources/offramp.move
@@ -0,0 +1,404 @@
+module ccip_offramp::offramp;
+
+use ccip::state_object;
+use ccip_offramp::ocr3_base;
+use std::ascii;
+use std::bcs;
+use std::string::{Self, String};
+use std::type_name;
+use sui::address;
+use sui::clock;
+use sui::derived_object;
+use sui::event;
+use sui::hash;
+use sui::package::{Self, UpgradeCap};
+use sui::table::{Self, Table};
+use sui::vec_map::{Self, VecMap};
+
+const EXECUTION_STATE_UNTOUCHED: u8 = 0;
+const EXECUTION_STATE_IN_PROGRESS: u8 = 1;
+const EXECUTION_STATE_SUCCESS: u8 = 2;
+const EXECUTION_STATE_FAILURE: u8 = 3;
+
+const ENothingToSend: u64 = 1;
+
+public struct OffRampObject has key {
+ id: UID,
+}
+
+public struct OffRampStatePointer has key, store {
+ id: UID,
+ off_ramp_object_id: address,
+}
+
+public struct StaticConfigSet has copy, drop {
+ chain_selector: u64,
+}
+
+public struct StaticConfig has copy, drop, store {
+ chain_selector: u64,
+ rmn_remote: address,
+ token_admin_registry: address,
+ nonce_manager: address,
+}
+
+public struct DynamicConfig has copy, drop, store {
+ fee_quoter: address,
+ permissionless_execution_threshold_seconds: u32, // The delay before manual exec is enabled
+}
+
+public struct SourceChainConfig has copy, drop, store {
+ router: address,
+ is_enabled: bool,
+ min_seq_nr: u64,
+ is_rmn_verification_disabled: bool,
+ on_ramp: vector,
+}
+
+public struct DynamicConfigSet has copy, drop {
+ dynamic_config: DynamicConfig,
+}
+
+public struct SourceChainConfigSet has copy, drop {
+ source_chain_selector: u64,
+ source_chain_config: SourceChainConfig,
+}
+
+public struct PriceUpdates has copy, drop, store {
+ token_price_updates: vector,
+ gas_price_updates: vector,
+}
+
+public struct TokenPriceUpdate has copy, drop, store {
+ source_token: address,
+ usd_per_token: u256,
+}
+
+public struct GasPriceUpdate has copy, drop, store {
+ dest_chain_selector: u64,
+ usd_per_unit_gas: u256,
+}
+
+public struct MerkleRoot has copy, drop, store {
+ source_chain_selector: u64,
+ on_ramp_address: vector,
+ min_seq_nr: u64,
+ max_seq_nr: u64,
+ merkle_root: vector,
+}
+
+public struct SkippedAlreadyExecuted has copy, drop {
+ source_chain_selector: u64,
+ sequence_number: u64,
+}
+
+public struct ExecutionStateChanged has copy, drop {
+ source_chain_selector: u64,
+ sequence_number: u64,
+ message_id: vector,
+ message_hash: vector,
+ state: u8,
+}
+
+public struct CommitReportAccepted has copy, drop {
+ blessed_merkle_roots: vector,
+ unblessed_merkle_roots: vector,
+ price_updates: PriceUpdates,
+}
+
+public struct SkippedReportExecution has copy, drop {
+ source_chain_selector: u64,
+}
+
+public struct OffRampState has key, store {
+ id: UID,
+ package_ids: vector,
+}
+
+public struct OFFRAMP has drop {}
+
+fun init(otw: OFFRAMP, ctx: &mut TxContext) {
+ let mut off_ramp_object = OffRampObject { id: object::new(ctx) };
+
+ let pointer = OffRampStatePointer {
+ id: object::new(ctx),
+ off_ramp_object_id: object::id_address(&off_ramp_object),
+ };
+
+ let tn = type_name::with_original_ids();
+ let package_bytes = ascii::into_bytes(tn.address_string());
+ let package_id = address::from_ascii_bytes(&package_bytes);
+
+ let state = OffRampState {
+ id: derived_object::claim(&mut off_ramp_object.id, b"OffRampState"),
+ package_ids: vector[package_id],
+ };
+
+ transfer::share_object(state);
+ transfer::share_object(off_ramp_object);
+
+ transfer::transfer(pointer, package_id);
+}
+
+public fun emit_commit_report_accepted(
+ min_seq_nr: u64,
+ max_seq_nr: u64,
+ onramp_address: address,
+ source_chain_selector: u64,
+) {
+ let mut merkle_root = vector[];
+ vector::append(&mut merkle_root, bcs::to_bytes(&onramp_address));
+ vector::append(&mut merkle_root, bcs::to_bytes(&min_seq_nr));
+ vector::append(&mut merkle_root, bcs::to_bytes(&max_seq_nr));
+
+ let merkle_root = MerkleRoot {
+ source_chain_selector,
+ on_ramp_address: bcs::to_bytes(&onramp_address),
+ min_seq_nr,
+ max_seq_nr,
+ merkle_root,
+ };
+
+ event::emit(CommitReportAccepted {
+ blessed_merkle_roots: vector[],
+ unblessed_merkle_roots: vector[merkle_root],
+ price_updates: PriceUpdates { token_price_updates: vector[], gas_price_updates: vector[] },
+ });
+}
+
+public fun emit_skipped_report_execution(source_chain_selector: u64) {
+ event::emit(SkippedReportExecution { source_chain_selector });
+}
+
+public fun emit_skipped_already_executed(source_chain_selector: u64, sequence_number: u64) {
+ event::emit(SkippedAlreadyExecuted { source_chain_selector, sequence_number });
+}
+
+public fun emit_execution_state_changed(source_chain_selector: u64, index: u64, ctx: &TxContext) {
+ let mut message_id = vector[];
+ message_id.append(bcs::to_bytes(&ctx.sender()));
+ message_id.append(bcs::to_bytes(&index));
+
+ let mut message_hash = vector[];
+ message_hash.append(bcs::to_bytes(&ctx.sender()));
+ message_hash.append(bcs::to_bytes(&index));
+
+ event::emit(ExecutionStateChanged {
+ source_chain_selector,
+ sequence_number: index,
+ message_id,
+ message_hash,
+ state: EXECUTION_STATE_SUCCESS,
+ });
+}
+
+public fun emit_execution_state_changed_untouched(
+ source_chain_selector: u64,
+ index: u64,
+ ctx: &TxContext,
+) {
+ let mut message_id = vector[];
+ message_id.append(bcs::to_bytes(&ctx.sender()));
+ message_id.append(bcs::to_bytes(&index));
+
+ let mut message_hash = vector[];
+ message_hash.append(bcs::to_bytes(&ctx.sender()));
+ message_hash.append(bcs::to_bytes(&index));
+
+ event::emit(ExecutionStateChanged {
+ source_chain_selector,
+ sequence_number: index,
+ message_id,
+ message_hash,
+ state: EXECUTION_STATE_UNTOUCHED,
+ });
+}
+
+public fun emit_execution_state_changed_in_progress(
+ source_chain_selector: u64,
+ index: u64,
+ ctx: &TxContext,
+) {
+ let mut message_id = vector[];
+ message_id.append(bcs::to_bytes(&ctx.sender()));
+ message_id.append(bcs::to_bytes(&index));
+
+ let mut message_hash = vector[];
+ message_hash.append(bcs::to_bytes(&ctx.sender()));
+ message_hash.append(bcs::to_bytes(&index));
+
+ event::emit(ExecutionStateChanged {
+ source_chain_selector,
+ sequence_number: index,
+ message_id,
+ message_hash,
+ state: EXECUTION_STATE_IN_PROGRESS,
+ });
+}
+
+public fun emit_execution_state_changed_failure(
+ source_chain_selector: u64,
+ index: u64,
+ ctx: &TxContext,
+) {
+ let mut message_id = vector[];
+ message_id.append(bcs::to_bytes(&ctx.sender()));
+ message_id.append(bcs::to_bytes(&index));
+
+ let mut message_hash = vector[];
+ message_hash.append(bcs::to_bytes(&ctx.sender()));
+ message_hash.append(bcs::to_bytes(&index));
+
+ event::emit(ExecutionStateChanged {
+ source_chain_selector,
+ sequence_number: index,
+ message_id,
+ message_hash,
+ state: EXECUTION_STATE_FAILURE,
+ });
+}
+
+public fun emit_static_config_set() {
+ let sui_selector = 17529533435026248318;
+ event::emit(StaticConfigSet { chain_selector: sui_selector });
+}
+
+public fun emit_dynamic_config_set(ref: &state_object::CCIPObjectRef, state: &OffRampState) {
+ let dynamic_config = get_dynamic_config(ref, state);
+ event::emit(DynamicConfigSet { dynamic_config });
+}
+
+public fun emit_source_chain_config_set() {
+ let source_chain_config = SourceChainConfig {
+ router: @ccip,
+ is_enabled: true,
+ min_seq_nr: 0,
+ is_rmn_verification_disabled: false,
+ on_ramp: bcs::to_bytes(&@onramp),
+ };
+ let sui_selector = 17529533435026248318;
+ event::emit(SourceChainConfigSet { source_chain_selector: sui_selector, source_chain_config });
+}
+
+public fun get_static_config(
+ ref: &state_object::CCIPObjectRef,
+ state: &OffRampState,
+): StaticConfig {
+ let sui_selector = 17529533435026248318;
+ StaticConfig {
+ chain_selector: sui_selector,
+ rmn_remote: @ccip,
+ token_admin_registry: @ccip,
+ nonce_manager: @ccip,
+ }
+}
+
+public fun get_dynamic_config(
+ ref: &state_object::CCIPObjectRef,
+ state: &OffRampState,
+): DynamicConfig {
+ DynamicConfig {
+ fee_quoter: @ccip,
+ permissionless_execution_threshold_seconds: 10 as u32,
+ }
+}
+
+public fun get_static_config_fields(
+ ref: &state_object::CCIPObjectRef,
+ cfg: StaticConfig,
+): (u64, address, address, address) {
+ (cfg.chain_selector, cfg.rmn_remote, cfg.token_admin_registry, cfg.nonce_manager)
+}
+
+public fun get_source_chain_config(
+ ref: &state_object::CCIPObjectRef,
+ state: &OffRampState,
+ source_chain_selector: u64,
+): SourceChainConfig {
+ SourceChainConfig {
+ router: @ccip,
+ is_enabled: true,
+ min_seq_nr: 0,
+ is_rmn_verification_disabled: false,
+ on_ramp: bcs::to_bytes(&@onramp),
+ }
+}
+
+public fun get_dynamic_config_fields(_: &state_object::CCIPObjectRef, cfg: DynamicConfig): (address, u32) {
+ (cfg.fee_quoter, cfg.permissionless_execution_threshold_seconds)
+}
+
+public fun type_and_version(): String {
+ string::utf8(b"OffRamp 1.6.0")
+}
+
+public fun emit_ocr3_base_config_set() {
+ ocr3_base::emit_config_set();
+}
+
+public fun get_ccip_package_id(): address {
+ @ccip
+}
+
+public fun get_all_source_chain_configs(
+ ref: &state_object::CCIPObjectRef,
+ state: &OffRampState,
+): (vector, vector) {
+ let sui_selector = 17529533435026248318;
+ let source_chain_selectors = vector[sui_selector];
+ let source_chain_config = SourceChainConfig {
+ router: @ccip,
+ is_enabled: true,
+ min_seq_nr: 2,
+ is_rmn_verification_disabled: false,
+ on_ramp: bcs::to_bytes(&@onramp),
+ };
+
+ (source_chain_selectors, vector[source_chain_config])
+}
+
+public fun add_package_id(state: &mut OffRampState, package_id: address) {
+ state.package_ids.push_back(package_id);
+}
+
+public fun remove_package_id(state: &mut OffRampState, package_id: address) {
+ let (found, idx) = state.package_ids.index_of(&package_id);
+ assert!(found, 1);
+ state.package_ids.swap_remove(idx);
+}
+
+public fun prepare_register() {
+ emit_source_chain_config_set();
+ emit_ocr3_base_config_set();
+}
+
+public fun drill_pending_execution(state: &OffRampState, from: u8, to: u8) {
+ assert!(from <= to, ENothingToSend);
+
+ emit_commit_report_accepted((from as u64), (to as u64), @onramp, 17529533435026248318);
+}
+
+public fun drill_offramp_initialize(ref: &state_object::CCIPObjectRef, state: &OffRampState) {
+ emit_static_config_set();
+ emit_dynamic_config_set(ref, state);
+ emit_source_chain_config_set();
+}
+
+public fun drill_offramp_execute(ctx: &TxContext) {
+ emit_skipped_already_executed(17529533435026248318, 1);
+ emit_skipped_report_execution(17529533435026248318);
+ emit_execution_state_changed(17529533435026248318, 1, ctx);
+}
+
+public fun get_source_chain_config_fields(
+ _: &state_object::CCIPObjectRef,
+ source_chain_config: SourceChainConfig,
+): (address, bool, u64, bool, vector) {
+ (
+ source_chain_config.router,
+ source_chain_config.is_enabled,
+ source_chain_config.min_seq_nr,
+ source_chain_config.is_rmn_verification_disabled,
+ source_chain_config.on_ramp,
+ )
+}
diff --git a/chains/sui/contracts/ccip_offramp/tests/ccip_offramp_tests.move b/chains/sui/contracts/ccip_offramp/tests/ccip_offramp_tests.move
new file mode 100644
index 0000000..5ff304f
--- /dev/null
+++ b/chains/sui/contracts/ccip_offramp/tests/ccip_offramp_tests.move
@@ -0,0 +1,18 @@
+/*
+#[test_only]
+module ccip_offramp::ccip_offramp_tests;
+// uncomment this line to import the module
+// use ccip_offramp::ccip_offramp;
+
+const ENotImplemented: u64 = 0;
+
+#[test]
+fun test_ccip_offramp() {
+ // pass
+}
+
+#[test, expected_failure(abort_code = ::ccip_offramp::ccip_offramp_tests::ENotImplemented)]
+fun test_ccip_offramp_fail() {
+ abort ENotImplemented
+}
+*/
diff --git a/chains/sui/contracts/ccip_onramp/.gitignore b/chains/sui/contracts/ccip_onramp/.gitignore
new file mode 100644
index 0000000..813de75
--- /dev/null
+++ b/chains/sui/contracts/ccip_onramp/.gitignore
@@ -0,0 +1,4 @@
+build/*
+traces/*
+.trace
+.coverage*
diff --git a/chains/sui/contracts/ccip_onramp/Move.lock b/chains/sui/contracts/ccip_onramp/Move.lock
new file mode 100644
index 0000000..3d171f1
--- /dev/null
+++ b/chains/sui/contracts/ccip_onramp/Move.lock
@@ -0,0 +1,68 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 3
+manifest_digest = "86679BEA99031DA7562CD5FAA553F62AB9851B6B11BD71C7EE11ABD757DBC6C7"
+deps_digest = "397E6A9F7A624706DBDFEE056CE88391A15876868FD18A88504DA74EB458D697"
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "ChainlinkCCIP", name = "ChainlinkCCIP" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "Bridge"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/bridge" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "ChainlinkCCIP"
+source = { local = "../ccip" }
+
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+id = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+]
+
+[[move.package]]
+id = "SuiSystem"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-system" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.61.2"
+edition = "2024.beta"
+flavor = "sui"
+
+[env]
+
+[env.testnet]
+chain-id = "4c78adac"
+original-published-id = "0xefd74f2ab89903cd0a6783af2937c8cb932812a41590d7842d632dd9770b3e68"
+latest-published-id = "0xefd74f2ab89903cd0a6783af2937c8cb932812a41590d7842d632dd9770b3e68"
+published-version = "1"
diff --git a/chains/sui/contracts/ccip_onramp/Move.toml b/chains/sui/contracts/ccip_onramp/Move.toml
new file mode 100644
index 0000000..b4c5760
--- /dev/null
+++ b/chains/sui/contracts/ccip_onramp/Move.toml
@@ -0,0 +1,15 @@
+[package]
+name = "CCIP_ONRAMP"
+edition = "2024.beta"
+
+[dependencies]
+ChainlinkCCIP = { local = "../ccip" }
+
+[addresses]
+ccip_onramp = "0x0"
+ccip = "0xe6763a05a2a7cb9250342d7420c566c6d4255920640c64c399beab47b6313e9b"
+router = "0x0d1cefe6e43cc96cbb6b2e7aa261b8be05cbf926a8f83431e1bd6023250b522c"
+
+[dev-addresses]
+ccip_onramp = "0x4000"
+ccip = "0x3000"
diff --git a/chains/sui/contracts/ccip_onramp/sources/onramp.move b/chains/sui/contracts/ccip_onramp/sources/onramp.move
new file mode 100644
index 0000000..0585913
--- /dev/null
+++ b/chains/sui/contracts/ccip_onramp/sources/onramp.move
@@ -0,0 +1,252 @@
+module ccip_onramp::onramp;
+
+use std::ascii;
+use std::bcs;
+use std::string::{Self, String};
+use std::type_name;
+use sui::address;
+use sui::derived_object;
+use sui::event;
+use sui::object;
+use sui::transfer;
+
+const ENothingToSend: u64 = 1;
+const EMessageAlreadySent: u64 = 2;
+
+public fun type_and_version(): String {
+ string::utf8(b"OnRamp 1.6.0")
+}
+
+public struct ONRAMP has drop {}
+
+public struct StaticConfig has drop, store {
+ chain_selector: u64
+}
+
+public struct DynamicConfig has store, drop, copy {
+ fee_aggregator: address,
+ allowlist_admin: address
+}
+
+public struct OnRampState has key, store {
+ id: UID,
+ package_ids: vector,
+ s_send_last: u64,
+}
+
+public struct OnRampObject has key {
+ id: UID,
+}
+
+public struct OnRampStatePointer has key, store {
+ id: UID,
+ on_ramp_object_id: address,
+}
+
+public struct RampMessageHeader has copy, drop, store {
+ message_id: vector,
+ source_chain_selector: u64,
+ dest_chain_selector: u64,
+ sequence_number: u64,
+ nonce: u64,
+}
+
+public struct Sui2AnyRampMessage has copy, drop, store {
+ header: RampMessageHeader,
+ sender: address,
+ data: vector,
+ receiver: vector,
+ extra_args: vector,
+ fee_token: address,
+ fee_token_amount: u64,
+ fee_value_juels: u256,
+ token_amounts: vector,
+}
+
+public struct Sui2AnyTokenTransfer has copy, drop, store {
+ source_pool_address: address,
+ // the token address on the destination chain
+ dest_token_address: vector,
+ extra_data: vector, // random bytes provided by token pool, e.g. encoded decimals
+ amount: u64,
+ dest_exec_data: vector, // destination gas amount
+}
+
+public struct CCIPMessageSent has copy, drop {
+ dest_chain_selector: u64,
+ sequence_number: u64,
+ message: Sui2AnyRampMessage,
+}
+
+public struct DestChainConfigSet has copy, drop {
+ dest_chain_selector: u64,
+ sequence_number: u64,
+ allowlist_enabled: bool,
+ router: address,
+}
+
+public struct AllowlistSendersAdded has copy, drop {
+ dest_chain_selector: u64,
+ senders: vector,
+}
+
+public struct AllowlistSendersRemoved has copy, drop {
+ dest_chain_selector: u64,
+ senders: vector,
+}
+
+fun init(otw: ONRAMP, ctx: &mut TxContext) {
+ let mut on_ramp_object = OnRampObject { id: object::new(ctx) };
+
+ let pointer = OnRampStatePointer {
+ id: object::new(ctx),
+ on_ramp_object_id: object::id_address(&on_ramp_object),
+ };
+
+ let tn = type_name::with_original_ids();
+ let package_bytes = ascii::into_bytes(tn.address_string());
+ let package_id = address::from_ascii_bytes(&package_bytes);
+
+ let state = OnRampState {
+ id: derived_object::claim(&mut on_ramp_object.id, b"OnRampState"),
+ package_ids: vector[package_id],
+ s_send_last: 0,
+ };
+
+ transfer::share_object(state);
+ transfer::share_object(on_ramp_object);
+
+ transfer::transfer(pointer, package_id);
+}
+
+public fun emit_dest_chain_config_set(router: address) {
+ let sui_selector = 17529533435026248318;
+ event::emit(DestChainConfigSet {
+ dest_chain_selector: sui_selector,
+ sequence_number: 0,
+ router: @ccip,
+ allowlist_enabled: false,
+ });
+}
+
+public fun get_static_config(_: &OnRampState): StaticConfig {
+ StaticConfig { chain_selector: 17529533435026248318 }
+}
+
+public fun get_static_config_fields(cfg: StaticConfig): u64 {
+ cfg.chain_selector
+}
+
+public fun get_dynamic_config_fields(cfg: DynamicConfig): (address, address) {
+ (cfg.fee_aggregator, cfg.allowlist_admin)
+}
+
+public fun get_dynamic_config(_: &OnRampState): DynamicConfig {
+ DynamicConfig { fee_aggregator: @ccip, allowlist_admin: @ccip }
+}
+
+public fun emit_allowlist_senders_added(dest_chain_selector: u64) {
+ event::emit(AllowlistSendersAdded { dest_chain_selector, senders: vector[] });
+}
+
+public fun emit_allowlist_senders_removed(dest_chain_selector: u64) {
+ event::emit(AllowlistSendersRemoved { dest_chain_selector, senders: vector[] });
+}
+
+public fun emit_ccip_message_sent(
+ index: u64,
+ fee_token: address,
+ receiver: address,
+ ctx: &TxContext,
+) {
+ let mut message_id = vector[];
+ message_id.append(bcs::to_bytes(&ctx.sender()));
+ message_id.append(bcs::to_bytes(&index));
+
+ let message = Sui2AnyRampMessage {
+ header: RampMessageHeader {
+ message_id,
+ source_chain_selector: 17529533435026248318,
+ dest_chain_selector: 17529533435026248318,
+ sequence_number: index,
+ nonce: 1,
+ },
+ sender: ctx.sender(),
+ data: b"123",
+ receiver: bcs::to_bytes(&receiver),
+ extra_args: b"123",
+ fee_token,
+ fee_token_amount: 0,
+ fee_value_juels: 0,
+ token_amounts: vector[],
+ };
+
+ event::emit(CCIPMessageSent {
+ dest_chain_selector: 17529533435026248318,
+ sequence_number: index,
+ message,
+ });
+}
+
+public fun get_ccip_package_id(): address {
+ @ccip
+}
+
+public fun get_dest_chain_config(
+ state: &OnRampState,
+ dest_chain_selector: u64,
+): (u64, bool, address) {
+ (1, false, @ccip)
+}
+
+public fun drill_onramp_initialize(router: address) {
+ emit_dest_chain_config_set(router);
+}
+
+public fun drill_allowlist_senders_added_removed() {
+ emit_allowlist_senders_added(17529533435026248318);
+ emit_allowlist_senders_removed(17529533435026248318);
+}
+
+public fun drill_pending_commit_pending_queue_tx_spike(
+ state: &mut OnRampState,
+ from: u8,
+ to: u8,
+ fee_token: address,
+ ctx: &TxContext,
+) {
+ assert!(from <= to, ENothingToSend);
+ assert!((from as u64) > state.s_send_last, EMessageAlreadySent);
+
+ let mut i = from;
+ while (i <= to) {
+ emit_ccip_message_sent((i as u64), fee_token, ctx.sender(), ctx);
+ i = i + 1;
+ };
+
+ state.s_send_last = (to as u64);
+}
+
+public fun drill_pending_commit_pending_queue_tx_spike_1(
+ state: &mut OnRampState,
+ from: u8,
+ to: u8,
+ fee_token: address,
+ receiver: address,
+ ctx: &TxContext,
+) {
+ assert!(from <= to, ENothingToSend);
+ assert!((from as u64) > state.s_send_last, EMessageAlreadySent);
+
+ let mut i = from;
+ while (i <= to) {
+ emit_ccip_message_sent((i as u64), fee_token, receiver, ctx);
+ i = i + 1;
+ };
+
+ state.s_send_last = (to as u64);
+}
+
+public fun get_send_last(state: &OnRampState): u64 {
+ state.s_send_last
+}
diff --git a/chains/sui/contracts/ccip_onramp/tests/ccip_onramp_tests.move b/chains/sui/contracts/ccip_onramp/tests/ccip_onramp_tests.move
new file mode 100644
index 0000000..ee53feb
--- /dev/null
+++ b/chains/sui/contracts/ccip_onramp/tests/ccip_onramp_tests.move
@@ -0,0 +1,18 @@
+/*
+#[test_only]
+module ccip_onramp::ccip_onramp_tests;
+// uncomment this line to import the module
+// use ccip_onramp::ccip_onramp;
+
+const ENotImplemented: u64 = 0;
+
+#[test]
+fun test_ccip_onramp() {
+ // pass
+}
+
+#[test, expected_failure(abort_code = ::ccip_onramp::ccip_onramp_tests::ENotImplemented)]
+fun test_ccip_onramp_fail() {
+ abort ENotImplemented
+}
+*/
diff --git a/chains/sui/contracts/ccip_receiver/.gitignore b/chains/sui/contracts/ccip_receiver/.gitignore
new file mode 100644
index 0000000..813de75
--- /dev/null
+++ b/chains/sui/contracts/ccip_receiver/.gitignore
@@ -0,0 +1,4 @@
+build/*
+traces/*
+.trace
+.coverage*
diff --git a/chains/sui/contracts/ccip_receiver/Move.lock b/chains/sui/contracts/ccip_receiver/Move.lock
new file mode 100644
index 0000000..a5d9292
--- /dev/null
+++ b/chains/sui/contracts/ccip_receiver/Move.lock
@@ -0,0 +1,56 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 3
+manifest_digest = "AAC1E10A15D2B6CA0C82AA89B410A7D65648226F238D0DB03D26B6370320F1E9"
+deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C"
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "Bridge"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/bridge" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+id = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+]
+
+[[move.package]]
+id = "SuiSystem"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-system" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.61.2"
+edition = "2024.beta"
+flavor = "sui"
+
+[env]
+
+[env.testnet]
+chain-id = "4c78adac"
+original-published-id = "0x3de4b0f75d94b6564378b757ff799401aec5d0bd2ecb7509b69fde9bc02388de"
+latest-published-id = "0x3de4b0f75d94b6564378b757ff799401aec5d0bd2ecb7509b69fde9bc02388de"
+published-version = "1"
diff --git a/chains/sui/contracts/ccip_receiver/Move.toml b/chains/sui/contracts/ccip_receiver/Move.toml
new file mode 100644
index 0000000..0a0c0b9
--- /dev/null
+++ b/chains/sui/contracts/ccip_receiver/Move.toml
@@ -0,0 +1,36 @@
+[package]
+name = "ccip_receiver"
+edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
+# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
+# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]
+
+[dependencies]
+
+# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
+# Revision can be a branch, a tag, and a commit hash.
+# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
+
+# For local dependencies use `local = path`. Path is relative to the package root
+# Local = { local = "../path/to" }
+
+# To resolve a version conflict and force a specific version for dependency
+# override use `override = true`
+# Override = { local = "../conflicting/version", override = true }
+
+[addresses]
+ccip_receiver = "0x0"
+
+# Named addresses will be accessible in Move as `@name`. They're also exported:
+# for example, `std = "0x1"` is exported by the Standard Library.
+# alice = "0xA11CE"
+
+[dev-dependencies]
+# The dev-dependencies section allows overriding dependencies for `--test` and
+# `--dev` modes. You can introduce test-only dependencies here.
+# Local = { local = "../path/to/dev-build" }
+
+[dev-addresses]
+# The dev-addresses section allows overwriting named addresses for the `--test`
+# and `--dev` modes.
+# alice = "0xB0B"
+
diff --git a/chains/sui/contracts/ccip_receiver/sources/ccip_receiver.move b/chains/sui/contracts/ccip_receiver/sources/ccip_receiver.move
new file mode 100644
index 0000000..bfd343b
--- /dev/null
+++ b/chains/sui/contracts/ccip_receiver/sources/ccip_receiver.move
@@ -0,0 +1,21 @@
+module ccip_receiver::ccip_receiver;
+
+const EInvalidTimestamp: u64 = 1;
+
+public struct CCIP_RECEIVER has drop {}
+
+public struct CCIPReceiverState has key, store {
+ id: UID,
+}
+
+fun init(_otw: CCIP_RECEIVER, ctx: &mut TxContext) {
+ let ccip_receiver_state = CCIPReceiverState {
+ id: object::new(ctx),
+ };
+
+ transfer::share_object(ccip_receiver_state);
+}
+
+public fun ccip_receive() {
+ assert!(1 > 2, EInvalidTimestamp);
+}
diff --git a/chains/sui/contracts/ccip_receiver/tests/ccip_receiver_tests.move b/chains/sui/contracts/ccip_receiver/tests/ccip_receiver_tests.move
new file mode 100644
index 0000000..843f239
--- /dev/null
+++ b/chains/sui/contracts/ccip_receiver/tests/ccip_receiver_tests.move
@@ -0,0 +1,18 @@
+/*
+#[test_only]
+module ccip_receiver::ccip_receiver_tests;
+// uncomment this line to import the module
+// use ccip_receiver::ccip_receiver;
+
+const ENotImplemented: u64 = 0;
+
+#[test]
+fun test_ccip_receiver() {
+ // pass
+}
+
+#[test, expected_failure(abort_code = ::ccip_receiver::ccip_receiver_tests::ENotImplemented)]
+fun test_ccip_receiver_fail() {
+ abort ENotImplemented
+}
+*/
diff --git a/chains/sui/contracts/ccip_router/.gitignore b/chains/sui/contracts/ccip_router/.gitignore
new file mode 100644
index 0000000..813de75
--- /dev/null
+++ b/chains/sui/contracts/ccip_router/.gitignore
@@ -0,0 +1,4 @@
+build/*
+traces/*
+.trace
+.coverage*
diff --git a/chains/sui/contracts/ccip_router/Move.lock b/chains/sui/contracts/ccip_router/Move.lock
new file mode 100644
index 0000000..d88f360
--- /dev/null
+++ b/chains/sui/contracts/ccip_router/Move.lock
@@ -0,0 +1,56 @@
+# @generated by Move, please check-in and do not edit manually.
+
+[move]
+version = 3
+manifest_digest = "07A725B2A701CF0EE14F47F75D20A3DD7E31CBCDD6609501137D0CC242E590AC"
+deps_digest = "F9B494B64F0615AED0E98FC12A85B85ECD2BC5185C22D30E7F67786BB52E507C"
+dependencies = [
+ { id = "Bridge", name = "Bridge" },
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "Bridge"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/bridge" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+ { id = "SuiSystem", name = "SuiSystem" },
+]
+
+[[move.package]]
+id = "MoveStdlib"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/move-stdlib" }
+
+[[move.package]]
+id = "Sui"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-framework" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+]
+
+[[move.package]]
+id = "SuiSystem"
+source = { git = "https://github.com/MystenLabs/sui.git", rev = "c1f1ae650fb9f9248b39a569400b4420820868db", subdir = "crates/sui-framework/packages/sui-system" }
+
+dependencies = [
+ { id = "MoveStdlib", name = "MoveStdlib" },
+ { id = "Sui", name = "Sui" },
+]
+
+[move.toolchain-version]
+compiler-version = "1.61.2"
+edition = "2024.beta"
+flavor = "sui"
+
+[env]
+
+[env.testnet]
+chain-id = "4c78adac"
+original-published-id = "0x0d1cefe6e43cc96cbb6b2e7aa261b8be05cbf926a8f83431e1bd6023250b522c"
+latest-published-id = "0x0d1cefe6e43cc96cbb6b2e7aa261b8be05cbf926a8f83431e1bd6023250b522c"
+published-version = "1"
diff --git a/chains/sui/contracts/ccip_router/Move.toml b/chains/sui/contracts/ccip_router/Move.toml
new file mode 100644
index 0000000..d8a17e8
--- /dev/null
+++ b/chains/sui/contracts/ccip_router/Move.toml
@@ -0,0 +1,36 @@
+[package]
+name = "ccip_router"
+edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move
+# license = "" # e.g., "MIT", "GPL", "Apache 2.0"
+# authors = ["..."] # e.g., ["Joe Smith (joesmith@noemail.com)", "John Snow (johnsnow@noemail.com)"]
+
+[dependencies]
+
+# For remote import, use the `{ git = "...", subdir = "...", rev = "..." }`.
+# Revision can be a branch, a tag, and a commit hash.
+# MyRemotePackage = { git = "https://some.remote/host.git", subdir = "remote/path", rev = "main" }
+
+# For local dependencies use `local = path`. Path is relative to the package root
+# Local = { local = "../path/to" }
+
+# To resolve a version conflict and force a specific version for dependency
+# override use `override = true`
+# Override = { local = "../conflicting/version", override = true }
+
+[addresses]
+ccip_router = "0x0"
+
+# Named addresses will be accessible in Move as `@name`. They're also exported:
+# for example, `std = "0x1"` is exported by the Standard Library.
+# alice = "0xA11CE"
+
+[dev-dependencies]
+# The dev-dependencies section allows overriding dependencies for `--test` and
+# `--dev` modes. You can introduce test-only dependencies here.
+# Local = { local = "../path/to/dev-build" }
+
+[dev-addresses]
+# The dev-addresses section allows overwriting named addresses for the `--test`
+# and `--dev` modes.
+# alice = "0xB0B"
+
diff --git a/chains/sui/contracts/ccip_router/sources/ccip_router.move b/chains/sui/contracts/ccip_router/sources/ccip_router.move
new file mode 100644
index 0000000..60a8bd8
--- /dev/null
+++ b/chains/sui/contracts/ccip_router/sources/ccip_router.move
@@ -0,0 +1,101 @@
+module ccip_router::router;
+
+use std::ascii;
+use std::string::{Self, String};
+use std::type_name;
+use sui::address;
+use sui::derived_object;
+use sui::event;
+use sui::object;
+use sui::transfer;
+use sui::vec_map::{Self, VecMap};
+
+public struct ROUTER has drop {}
+
+const EOnrampNotFound: u64 = 1;
+const EParamsLengthMismatch: u64 = 2;
+const EInvalidOnrampAddress: u64 = 3;
+
+public struct RouterObject has key {
+ id: UID,
+}
+
+public struct OnRampSet has copy, drop {
+ dest_chain_selector: u64,
+ on_ramp_package_id: address,
+}
+
+public struct RouterState has key {
+ id: UID,
+ on_ramp_package_ids: VecMap, // dest_chain_selector -> on_ramp_package_id
+}
+
+public struct RouterStatePointer has key, store {
+ id: UID,
+ router_object_id: address,
+}
+
+public fun type_and_version(): String {
+ string::utf8(b"Router 1.6.0")
+}
+
+fun init(otw: ROUTER, ctx: &mut TxContext) {
+ let mut router_object = RouterObject { id: object::new(ctx) };
+
+ let router = RouterState {
+ id: derived_object::claim(&mut router_object.id, b"RouterState"),
+ on_ramp_package_ids: vec_map::empty(),
+ };
+
+ let router_state_pointer = RouterStatePointer {
+ id: object::new(ctx),
+ router_object_id: object::id_address(&router_object),
+ };
+
+ let tn = type_name::with_original_ids();
+ let package_bytes = ascii::into_bytes(tn.address_string());
+ let package_id = address::from_ascii_bytes(&package_bytes);
+
+ transfer::share_object(router);
+ transfer::share_object(router_object);
+
+ transfer::transfer(router_state_pointer, package_id);
+}
+
+public fun is_chain_supported(router: &RouterState, dest_chain_selector: u64): bool {
+ router.on_ramp_package_ids.contains(&dest_chain_selector)
+}
+
+// Returns the on ramp package id for the given destination chain selector.
+public fun get_on_ramp(router: &RouterState, dest_chain_selector: u64): address {
+ assert!(router.on_ramp_package_ids.contains(&dest_chain_selector), EOnrampNotFound);
+
+ *router.on_ramp_package_ids.get(&dest_chain_selector)
+}
+
+public fun get_dest_chains(router: &RouterState): vector {
+ router.on_ramp_package_ids.keys()
+}
+
+public fun set_on_ramps(
+ router: &mut RouterState,
+ dest_chain_selectors: vector,
+ on_ramp_package_ids: vector,
+) {
+ assert!(dest_chain_selectors.length() == on_ramp_package_ids.length(), EParamsLengthMismatch);
+
+ let mut i = 0;
+ let selector_len = dest_chain_selectors.length();
+ while (i < selector_len) {
+ let dest_chain_selector = dest_chain_selectors[i];
+ let on_ramp_package_id = on_ramp_package_ids[i];
+ assert!(on_ramp_package_id != @0x0, EInvalidOnrampAddress);
+
+ if (router.on_ramp_package_ids.contains(&dest_chain_selector)) {
+ router.on_ramp_package_ids.remove(&dest_chain_selector);
+ };
+ router.on_ramp_package_ids.insert(dest_chain_selector, on_ramp_package_id);
+ event::emit(OnRampSet { dest_chain_selector, on_ramp_package_id });
+ i = i + 1;
+ };
+}
diff --git a/chains/sui/contracts/ccip_router/tests/ccip_router_tests.move b/chains/sui/contracts/ccip_router/tests/ccip_router_tests.move
new file mode 100644
index 0000000..b3e43cf
--- /dev/null
+++ b/chains/sui/contracts/ccip_router/tests/ccip_router_tests.move
@@ -0,0 +1,18 @@
+/*
+#[test_only]
+module ccip_router::ccip_router_tests;
+// uncomment this line to import the module
+// use ccip_router::ccip_router;
+
+const ENotImplemented: u64 = 0;
+
+#[test]
+fun test_ccip_router() {
+ // pass
+}
+
+#[test, expected_failure(abort_code = ::ccip_router::ccip_router_tests::ENotImplemented)]
+fun test_ccip_router_fail() {
+ abort ENotImplemented
+}
+*/