Skip to content

Sui rfq deploy#12

Open
reddyismav wants to merge 3 commits into
mainfrom
sui-rfq-deploy
Open

Sui rfq deploy#12
reddyismav wants to merge 3 commits into
mainfrom
sui-rfq-deploy

Conversation

@reddyismav

@reddyismav reddyismav commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added Sui RFQ vault support, including deployment tooling, configuration, and production address tracking.
    • Introduced a vault contract with deposit, fulfil, refund, pause/unpause, ownership transfer, and rescue capabilities.
    • Added a helper command to derive and print the Sui solver key from AWS KMS.
  • Bug Fixes

    • Improved validation for vault setup, signatures, quote IDs, and deployment inputs.
    • Added tests covering replay protection, refund flows, pause behavior, and owner-only actions.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Introduces a complete Sui Move RFQ vault contract (rfq_vault::vault) with deposit, fulfil, mark-for-refund, refund, and admin entrypoints backed by secp256k1 signature verification. Adds TypeScript scripts to deploy the vault via Sui CLI and derive solver keys from AWS KMS, plus deployment configuration, production address records, and a full Move test suite.

Changes

Sui RFQ Vault Move Contract

Layer / File(s) Summary
Vault structs, constants, and package manifest
sui/rfq-vault/Move.toml, sui/rfq-vault/Published.toml, sui/rfq-vault/sources/vault.move (L1–58)
Defines the Move package manifest, publication metadata, module imports, public error/action/length constants, and all on-chain and event structs (Vault, Deposited, Fulfilled, MarkedForRefund, Refunded, SettlementMessage, MarkForRefundMessage).
Core vault entrypoints: create, deposit, fulfil, mark_for_refund, refund
sui/rfq-vault/sources/vault.move (L85–195)
Implements create_vault with input validation and shared-object creation; deposit with pause/quote-id enforcement and per-token balance upsert; fulfil, mark_for_refund, and refund each serializing a message, verifying secp256k1 signature (SHA-256), atomically marking quote IDs, and transferring tokens.
Admin entrypoints, accessors, message builders, and internal helpers
sui/rfq-vault/sources/vault.move (L197–408)
Adds owner-restricted fund/rescue/pause/unpause/set_solver_pubkey/transfer_ownership, read-only accessors, public message-building functions, internal BCS serializer, verify_signature, balance CRUD helpers, validation guards, and token-type key derivation.
Move test suite
sui/rfq-vault/tests/vault_tests.move
Covers deposit idempotency, fulfil/refund/mark-for-refund happy paths, replay prevention (all three action types), pause enforcement, owner-only rescue, invalid quote-id rejection, separate-owner creation, plus setup/signing/quote-id helpers.

Deployment Scripts and Configuration

Layer / File(s) Summary
Deployment config, KMS solver key script, and wiring
scripts/sui/rfqVaultDeploymentConfig.ts, scripts/sui/printKmsSolverKey.ts, package.json, .gitignore
Exports mainnet deployment constants; adds printKmsSolverKey.ts that calls AWS KMS, extracts SPKI DER, compresses secp256k1 key, derives Sui (blake2b) and EVM (keccak256) addresses; wires new npm scripts and @noble/hashes dep; adds .gitignore rules for Sui build output and .cursor/rules/.
deployRfqVault.ts: Sui CLI deployment orchestration
scripts/sui/deployRfqVault.ts
Full deployment main flow: config/deployer validation, temp Sui config dir, sequential CLI steps (build → create-env → import key → publish → transfer UpgradeCap → create_vault), deployment JSON persistence with merge, optional source verification, secret redaction, and guaranteed cleanup.
Production deployment address record
deployments/prod/addresses/sui.json
Commits mainnet package ID, vault contract address, upgrade cap, owner/solver key/domain, network alias, and publish/transfer/create transaction digests.

Sequence Diagram(s)

sequenceDiagram
  participant Solver
  participant Caller
  participant Vault
  participant ecdsa_k1
  rect rgba(100, 149, 237, 0.5)
    note over Caller,Vault: Deposit
    Caller->>Vault: deposit(quote_id, coin)
    Vault->>Vault: assert_not_paused, assert_valid_quote_id
    Vault->>Vault: balance_mut_or_create~T~, join amount
    Vault-->>Caller: emit Deposited
  end
  rect rgba(144, 238, 144, 0.5)
    note over Solver,Vault: Fulfil
    Solver->>Vault: fulfil(quote_id, nonce, amount, receiver, signature)
    Vault->>Vault: build fulfil_message bytes (BCS)
    Vault->>ecdsa_k1: secp256k1_verify(sig, msg, solver_pubkey, SHA256)
    ecdsa_k1-->>Vault: ok / abort E_INVALID_SIGNATURE
    Vault->>Vault: mark_quote_id (abort if reused)
    Vault->>Vault: existing_balance_mut~T~, split coin
    Vault-->>Solver: transfer coin to receiver, emit Fulfilled
  end
  rect rgba(255, 160, 122, 0.5)
    note over Solver,Vault: Refund path
    Solver->>Vault: mark_for_refund(quote_id, nonce, signature)
    Vault->>ecdsa_k1: secp256k1_verify
    Vault->>Vault: mark quote_used + marked_for_refund
    Solver->>Vault: refund(quote_id, nonce, amount, receiver, signature)
    Vault->>ecdsa_k1: secp256k1_verify
    Vault->>Vault: mark_quote_id (abort if reused)
    Vault-->>Solver: transfer coin to receiver, emit Refunded
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • tHeMaskedMan981

Poem

A vault on Sui, secp256k1 blessed,
KMS hands the solver key compressed,
Fulfil, refund, mark for return —
Quote IDs single-use, no replay concern.
deployRfqVault wires CLI calls tight,
Tokens move on-chain — production's alight. 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too generic and does not clearly describe the main change, which adds a Sui RFQ vault contract and deployment tooling. Rename it to something specific like "Add Sui RFQ vault contract and deployment scripts".
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sui-rfq-deploy

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/sui/deployRfqVault.ts`:
- Around line 423-448: Move the deployment persistence in deployRfqVault so the
record is written only after source verification passes. The current
persistDeployment call in deployRfqVault runs before the config.verifySource
branch and can save an unverified package; relocate that call to after the
runSui('verify-source', ...) step succeeds, keeping the same deployment fields
and using the existing packageId/vaultId data.
- Around line 316-331: The deployer secret is being passed as a positional
argument to the `runSui` call that invokes `keytool import`, which exposes it
through argv/process listings. Update the `deployRfqVault` flow to avoid passing
`deployer.secret` on the command line by using a non-argv import path such as a
preloaded isolated keystore or another supported stdin/file-based mechanism,
while keeping the existing `DEPLOYER_ALIAS` import behavior intact.

In `@sui/rfq-vault/sources/vault.move`:
- Around line 115-116: The deposit flow in vault.move currently only checks
assert_not_paused and assert_valid_quote_id, so it can still accept a quote_id
that has already been fulfilled, refunded, or marked for refund. Update the
deposit entry path in the vault module to reject any consumed quote_id by adding
a single-use state check alongside assert_valid_quote_id, using the existing
quote lifecycle helpers/state transitions used elsewhere in the module. Ensure
the deposit function aborts before funds are accepted when the quote_id is no
longer active.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4f4af8d-59e6-4a99-a2cf-3aeb6e880273

📥 Commits

Reviewing files that changed from the base of the PR and between 78519c8 and f7ac83a.

⛔ Files ignored due to path filters (1)
  • sui/rfq-vault/Move.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • .gitignore
  • deployments/prod/addresses/sui.json
  • package.json
  • scripts/sui/deployRfqVault.ts
  • scripts/sui/printKmsSolverKey.ts
  • scripts/sui/rfqVaultDeploymentConfig.ts
  • sui/rfq-vault/Move.toml
  • sui/rfq-vault/Published.toml
  • sui/rfq-vault/sources/vault.move
  • sui/rfq-vault/tests/vault_tests.move

Comment on lines +316 to +331
runSui(
[
'keytool',
'import',
'--alias',
DEPLOYER_ALIAS,
'--json',
deployer.secret,
deployer.scheme,
],
{
env,
label: 'import SUI_DEPLOYER into temporary keystore',
redactions: [deployer.secret],
},
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if ! command -v sui >/dev/null 2>&1; then
  echo "sui CLI not installed here; check 'sui keytool import --help' locally for stdin/file/prompt options."
  exit 0
fi

sui keytool import --help | sed -n '1,200p'

Repository: SocketDotTech/openrouter

Length of output: 263


🌐 Web query:

Sui CLI keytool import help stdin file prompt import secret keytool import --help documentation

💡 Result:

The sui keytool import command is used to add a new key to the Sui CLI Keystore [1][2]. The command accepts either a mnemonic phrase or a Bech32-encoded private key starting with the prefix suiprivkey [1][3]. Usage and Syntax The basic syntax for the command is: sui keytool import '' <KEY_SCHEME> Parameters: INPUT: A mnemonic phrase (e.g., 12, 15, 18, 21, or 24 words) or a Bech32-encoded 33-byte flag || privkey string starting with suiprivkey [1][4]. KEY_SCHEME: The signature scheme, which must be one of ed25519, secp256k1, or secp256r1 [1][4]. Important Notes: 1. Handling Stdin and Prompting: The Sui CLI does not natively support an explicit flag to pipe a secret key from stdin into the import command [4]. It expects the input string as a positional argument [4]. When using a mnemonic phrase, it is recommended to wrap the entire phrase in single quotes to ensure it is processed correctly as a single argument [5]. 2. Hex Format Deprecation: Sui no longer supports importing private keys in Hex format [6][7]. If you have a legacy Hex-encoded private key, you must first convert it to the required Bech32 format using the sui keytool convert <HEX_KEY> command [6][7]. 3. Aliases: You can assign an alias to the imported key using the --alias flag [1][4]. If no alias is provided, the tool will automatically generate one [1][4]. 4. Help Documentation: To view the full command help and options directly in your terminal, run: sui keytool import --help

Citations:


Keep the deployer secret out of argv. Line 323 passes deployer.secret to sui keytool import as a positional argument, and that interface exposes the secret in process listings/procfs on shared CI or hosts. This should use a preloaded isolated keystore or another non-argv import path.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sui/deployRfqVault.ts` around lines 316 - 331, The deployer secret is
being passed as a positional argument to the `runSui` call that invokes `keytool
import`, which exposes it through argv/process listings. Update the
`deployRfqVault` flow to avoid passing `deployer.secret` on the command line by
using a non-argv import path such as a preloaded isolated keystore or another
supported stdin/file-based mechanism, while keeping the existing
`DEPLOYER_ALIAS` import behavior intact.

Comment on lines +423 to +448
const filePath = await persistDeployment(config.stage, {
SuiRFQVaultPackage: packageId,
SuiRFQVault: vaultId,
SuiRFQVaultUpgradeCap: upgradeCapId,
SuiRFQVaultPublishTx: publishResult.digest,
SuiRFQVaultUpgradeCapTransferTx: upgradeCapTransferDigest,
SuiRFQVaultCreateTx: createVaultResult.digest,
SuiRFQVaultOwner: config.ownerAddress,
SuiRFQVaultSolverPublicKey: config.solverPublicKey,
SuiRFQVaultDomain: config.domain,
SuiRFQVaultNetworkAlias: config.networkAlias,
});

if (config.verifySource) {
runSui(
[
'client',
'--client.config',
clientConfig,
'--json',
'verify-source',
packagePath,
'--verify-deps',
],
{ env, label: 'verify published source' },
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist the deployment record after verification succeeds.

Line 423 writes deployments/prod/addresses/sui.json before Line 436 runs source verification. A failed verification still leaves an updated production address record for an unverified package. This should move persistDeployment below the verification gate.

Proposed ordering fix
-    const filePath = await persistDeployment(config.stage, {
-      SuiRFQVaultPackage: packageId,
-      SuiRFQVault: vaultId,
-      SuiRFQVaultUpgradeCap: upgradeCapId,
-      SuiRFQVaultPublishTx: publishResult.digest,
-      SuiRFQVaultUpgradeCapTransferTx: upgradeCapTransferDigest,
-      SuiRFQVaultCreateTx: createVaultResult.digest,
-      SuiRFQVaultOwner: config.ownerAddress,
-      SuiRFQVaultSolverPublicKey: config.solverPublicKey,
-      SuiRFQVaultDomain: config.domain,
-      SuiRFQVaultNetworkAlias: config.networkAlias,
-    });
-
     if (config.verifySource) {
       runSui(
         [
@@
         { env, label: 'verify published source' },
       );
     }
+
+    const filePath = await persistDeployment(config.stage, {
+      SuiRFQVaultPackage: packageId,
+      SuiRFQVault: vaultId,
+      SuiRFQVaultUpgradeCap: upgradeCapId,
+      SuiRFQVaultPublishTx: publishResult.digest,
+      SuiRFQVaultUpgradeCapTransferTx: upgradeCapTransferDigest,
+      SuiRFQVaultCreateTx: createVaultResult.digest,
+      SuiRFQVaultOwner: config.ownerAddress,
+      SuiRFQVaultSolverPublicKey: config.solverPublicKey,
+      SuiRFQVaultDomain: config.domain,
+      SuiRFQVaultNetworkAlias: config.networkAlias,
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const filePath = await persistDeployment(config.stage, {
SuiRFQVaultPackage: packageId,
SuiRFQVault: vaultId,
SuiRFQVaultUpgradeCap: upgradeCapId,
SuiRFQVaultPublishTx: publishResult.digest,
SuiRFQVaultUpgradeCapTransferTx: upgradeCapTransferDigest,
SuiRFQVaultCreateTx: createVaultResult.digest,
SuiRFQVaultOwner: config.ownerAddress,
SuiRFQVaultSolverPublicKey: config.solverPublicKey,
SuiRFQVaultDomain: config.domain,
SuiRFQVaultNetworkAlias: config.networkAlias,
});
if (config.verifySource) {
runSui(
[
'client',
'--client.config',
clientConfig,
'--json',
'verify-source',
packagePath,
'--verify-deps',
],
{ env, label: 'verify published source' },
);
if (config.verifySource) {
runSui(
[
'client',
'--client.config',
clientConfig,
'--json',
'verify-source',
packagePath,
'--verify-deps',
],
{ env, label: 'verify published source' },
);
}
const filePath = await persistDeployment(config.stage, {
SuiRFQVaultPackage: packageId,
SuiRFQVault: vaultId,
SuiRFQVaultUpgradeCap: upgradeCapId,
SuiRFQVaultPublishTx: publishResult.digest,
SuiRFQVaultUpgradeCapTransferTx: upgradeCapTransferDigest,
SuiRFQVaultCreateTx: createVaultResult.digest,
SuiRFQVaultOwner: config.ownerAddress,
SuiRFQVaultSolverPublicKey: config.solverPublicKey,
SuiRFQVaultDomain: config.domain,
SuiRFQVaultNetworkAlias: config.networkAlias,
});
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawnSync } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/sui/deployRfqVault.ts` around lines 423 - 448, Move the deployment
persistence in deployRfqVault so the record is written only after source
verification passes. The current persistDeployment call in deployRfqVault runs
before the config.verifySource branch and can save an unverified package;
relocate that call to after the runSui('verify-source', ...) step succeeds,
keeping the same deployment fields and using the existing packageId/vaultId
data.

Comment on lines +115 to +116
assert_not_paused(vault);
assert_valid_quote_id(&quote_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject deposits for consumed quote IDs.

This should abort once a quote_id has already been fulfilled, refunded, or marked for refund. The rest of the module treats quote_id as single-use, so accepting a later deposit strands funds in the pooled balance with no matching settlement path.

Proposed fix
     assert_not_paused(vault);
     assert_valid_quote_id(&quote_id);
+    assert!(
+        !table::contains<vector<u8>, bool>(&vault.quote_used, copy quote_id),
+        E_INVALID_QUOTE_ID,
+    );
 
     let amount = coin::value<T>(&coin);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert_not_paused(vault);
assert_valid_quote_id(&quote_id);
assert_not_paused(vault);
assert_valid_quote_id(&quote_id);
assert!(
!table::contains<vector<u8>, bool>(&vault.quote_used, copy quote_id),
E_INVALID_QUOTE_ID,
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sui/rfq-vault/sources/vault.move` around lines 115 - 116, The deposit flow in
vault.move currently only checks assert_not_paused and assert_valid_quote_id, so
it can still accept a quote_id that has already been fulfilled, refunded, or
marked for refund. Update the deposit entry path in the vault module to reject
any consumed quote_id by adding a single-use state check alongside
assert_valid_quote_id, using the existing quote lifecycle helpers/state
transitions used elsewhere in the module. Ensure the deposit function aborts
before funds are accepted when the quote_id is no longer active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant