> For the complete documentation index, see [llms.txt](https://bluwhale.gitbook.io/bluwhaleai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bluwhale.gitbook.io/bluwhaleai/oceanum-oxn/smart-contract-development/testing-confidential-behavior.md).

# Testing Confidential Behavior

Local emulators cannot test Oceanum (OXN)'s confidentiality features — encrypted calldata, encrypted storage, signed queries, and confidential precompiles all require the Oceanum (OXN) runtime. This page covers how to build integration tests that actually exercise those paths.

### Test target <a href="#test-target" id="test-target"></a>

Point your integration tests at **Oceanum (OXN) Testnet**:

```
RPC URL:   https://rpc.bout.network
Chain ID:  186
```

Yes, this means your tests use real network calls, real gas, and real (testnet) tokens from the faucet. That's the point — the whole confidentiality stack only exists on the real network.

### Basic integration test structure <a href="#basic-integration-test-structure" id="basic-integration-test-structure"></a>

test/confidential.integration.js

```
const { expect } = require("chai");
const { ethers } = require("hardhat");

const CHAIN_ID = 186;

describe("MyToken confidential behavior [integration]", function () {
  let token;
  let signer;
  let rawProvider;      // unwrapped, for reading raw on-chain data

  this.timeout(120_000);   // network calls are slower than local

  before(async function () {
    // The Hardhat plugin auto-wraps this signer for encryption
    [signer] = await ethers.getSigners();
    const chainId = (await ethers.provider.getNetwork()).chainId;
    expect(chainId).to.equal(CHAIN_ID);   // guard against running on local network

    // Deploy
    const Token = await ethers.getContractFactory("MyToken");
    token = await Token.deploy(ethers.parseUnits("1000000", 18), { gasLimit: 3_000_000n });
    await token.waitForDeployment();

    // Raw provider for reading unencrypted on-chain data
    rawProvider = new ethers.JsonRpcProvider("https://rpc.bout.network");
  });

  it("transfer calldata is encrypted on-chain", async function () {
    const recipient = "0x000000000000000000000000000000000000dEaD";

    const tx = await token.transfer(recipient, ethers.parseUnits("100", 18),
                                    { gasLimit: 500_000n });
    await tx.wait();

    // Fetch the raw on-chain transaction
    const onchain = await rawProvider.getTransaction(tx.hash);

    // If encrypted, tx.data starts with 0xa? (CBOR map). If plain, 0xa9059cbb (transfer).
    const firstByte = parseInt(onchain.data.slice(2, 4), 16);
    expect(firstByte & 0xe0).to.equal(0xa0);
    expect(onchain.data.startsWith("0xa9059cbb")).to.equal(false);
  });
});
```

Two things worth calling out:

1. **The `firstByte & 0xe0 === 0xa0` assertion** is the reliable way to confirm encryption. It checks that the tx data is a CBOR envelope.
2. **The `rawProvider`** is the unwrapped ethers provider — you need it to see the raw on-chain data. If you use `ethers.provider` (which is wrapped), you get decrypted data and the assertion is meaningless.

### Testing signed queries <a href="#testing-signed-queries" id="testing-signed-queries"></a>

Signed queries populate `msg.sender` inside a view function. Verify the pattern works:

```
it("myBalance() returns caller's balance via signed query", async function () {
  const [caller, other] = await ethers.getSigners();

  // Deploy a token that assigns balances to specific accounts
  const token = /* ... */;

  // Wrap the contract with the caller's signer — this triggers signed queries
  const asCaller = token.connect(caller);
  const balance = await asCaller.myBalance();

  expect(balance).to.equal(expectedCallerBalance);

  // Same call, different signer
  const asOther = token.connect(other);
  const otherBalance = await asOther.myBalance();
  expect(otherBalance).to.equal(0);   // other has no tokens
});
```

The key move is `token.connect(signer)` — passing a signer (not a provider) is what tells the SDK to issue a signed query.

### Testing that logs are decryptable only by the caller <a href="#testing-that-logs-are-decryptable-only-by-the-caller" id="testing-that-logs-are-decryptable-only-by-the-caller"></a>

```
it("emitted event is decryptable to the caller only", async function () {
  const [caller, other] = await ethers.getSigners();

  const tx = await token.connect(caller).transfer(other.address, 100, { gasLimit: 500_000n });
  const receipt = await tx.wait();

  // The caller's signer can decrypt events emitted by this call
  const decodedByCaller = receipt.logs
    .map((log) => { try { return token.interface.parseLog(log); } catch { return null; } })
    .filter(Boolean);
  expect(decodedByCaller.length).to.be.greaterThan(0);

  // A raw provider reading the same tx sees ciphertext
  const rawReceipt = await rawProvider.getTransactionReceipt(tx.hash);
  for (const log of rawReceipt.logs) {
    // topics and data are ciphertext bytes, not the standard Transfer signature
    expect(log.topics[0]).to.not.equal(ethers.id("Transfer(address,address,uint256)"));
  }
});
```

### Managing testnet accounts in CI <a href="#managing-testnet-accounts-in-ci" id="managing-testnet-accounts-in-ci"></a>

Testnet integration tests need funded accounts. Options:

1. **Fund a set of accounts once, reuse them.** Store the private keys as encrypted CI secrets.
2. **Fund a "manager" account, have it fund test accounts as needed.** The manager holds a balance; each test run drains a small amount to fresh accounts.
3. **Use the faucet in CI.** The faucet is not designed for programmatic use; contact the team via [Support](https://docs.bout.network/resources/support) if you have a legitimate CI need.

Do **not** commit private keys, even for testnet accounts — the accounts may be reused across environments over time, or accidentally used on mainnet.

### Testing gas costs <a href="#testing-gas-costs" id="testing-gas-costs"></a>

Since gas is set explicitly on OXN, you cannot assert exact gas values via estimation in tests the way you would on Ethereum. Test with generous fixed `gasLimit` values and assert that the transaction succeeds; separately, use `receipt.gasUsed` to log observed costs for regression tracking, but do not fail the build on small variations.

### Common integration-test failures <a href="#common-integration-test-failures" id="common-integration-test-failures"></a>

**"Test hangs then times out."** The RPC endpoint or faucet may be under load. Retry, or check the [status page](https://docs.bout.network/resources/status) *(coming soon)*.

**"tx.data is plain even though I used wrapped signer."** Verify that your Hardhat config `require("@oasisprotocol/sapphire-hardhat")` **after** `require("@nomicfoundation/hardhat-toolbox")`. Order matters.

**"myBalance() returns 0 for all accounts."** You sent a plain `eth_call` instead of a signed query. Ensure the `Contract` was constructed with a signer, not a provider.

### Next steps <a href="#next-steps" id="next-steps"></a>

* [**Deploying Contracts**](https://docs.bout.network/build/deploying)
* [**Debugging & Troubleshooting**](https://docs.bout.network/build/debugging)
* [**Testing Contracts Locally**](https://docs.bout.network/build/testing-local) — the local layer this complements

[Edit this page](https://github.com/oxn-network/oxn-docs/edit/main/docs/06-build/testing-confidential.md)[PreviousTesting Contracts Locally](https://docs.bout.network/build/testing-local)[NextDeploying Contracts](https://docs.bout.network/build/deploying)
