Skip to content

feat: interop for ethers adapter#58

Merged
dutterbutter merged 33 commits intomainfrom
vi/ethers-adapter-interop
Feb 13, 2026
Merged

feat: interop for ethers adapter#58
dutterbutter merged 33 commits intomainfrom
vi/ethers-adapter-interop

Conversation

@vasyl-ivanchuk
Copy link
Contributor

@vasyl-ivanchuk vasyl-ivanchuk commented Feb 9, 2026

What 💻

This PR implements interop for ethers adapter.

Why ✋

So the SDK can be used for interop transactions.

Evidence 📷

Interop usage example:

const params = {
    dstChain: l2Destination,
    actions: [
      {
        type: 'call' as const,
        to: greeterAddress,
        data: data,
      },
    ],
    // Optional bundle-level execution constraints:
    // execution: { only: someExecAddress },
    // unbundling: { by: someUnbundlerAddress },
  };

  // QUOTE: Build and return the summary.
  const quote = await sdk.interop.quote(params);
  console.log('QUOTE:', quote);

  // PREPARE: Build plan without executing.
  const prepared = await sdk.interop.prepare(params);
  console.log('PREPARE:', prepared);

  // CREATE: Execute the source-chain step(s), wait for each tx receipt to confirm (status != 0).
  const created = await sdk.interop.create(params);
  console.log('CREATE:', created);

  // STATUS: Non-blocking lifecycle inspection.
  const st0 = await sdk.interop.status(created);
  console.log('STATUS after create:', st0);

  // WAIT: waits until the L2->L1 proof is available on source and the interop root
  // becomes available on the destination chain. It returns the proof payload needed
  // to execute the bundle later.
  const finalizationInfo = await sdk.interop.wait(created, {
    pollMs: 5_000,
    timeoutMs: 30 * 60 * 1_000,
  });
  console.log('Bundle is finalized on source; root available on destination.');
  // FINALIZE: Execute on destination and block until done.
  // finalize() calls executeBundle(...) on the destination chain,
  // waits for the tx to mine, then returns { bundleHash, dstChainId, dstExecTxHash }.
  const finalizationResult = await sdk.interop.finalize(finalizationInfo);
  console.log('FINALIZE RESULT:', finalizationResult);

  // STATUS: Terminal status (EXECUTED).
  const st1 = await sdk.interop.status(created);
  console.log('STATUS after finalize:', st1);

How to test:

There are two working examples that can be used for testing:

ERC20 Transfer

Deploys an ERC20 on the source chain and transfers 100 tokens to the destination chain. NTV registration and token approval are handled automatically.

Remote Call

Deploys a Greeting contract on the destination chain and checks its message. Then, from the source chain, it sends an interop call to change the message. It confirms the message has changed by checking the value once again.

How to run them:

  1. run the multi-chain ZKsync OS setup:
  • fetch latest from the zksync-os-server main branch.
  • run ./run_local.sh ./local-chains/v31.0/multi_chain.
  1. fund the address you will use on both the source and destination chains.
  2. run PRIVATE_KEY=<PK> bun run ./examples/ethers/interop/<erc20-transfer.ts|remote-call.ts>.

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

ci-run-tests LCOV of commit d1cc38f during ci-run-tests #181

Summary coverage rate:
  lines......: 83.9% (13390 of 15953 lines)
  functions..: no data found
  branches...: no data found

Files changed coverage rate:
                                                                              |Lines       |Functions  |Branches    
  Filename                                                                    |Rate     Num|Rate    Num|Rate     Num
  ==================================================================================================================
  src/adapters/__tests__/adapter-harness.ts                                   | 0.0%    435|    -     0|    -      0
  src/adapters/__tests__/decode-helpers.ts                                    | 0.0%    100|    -     0|    -      0
  src/adapters/ethers/client.ts                                               | 0.0%    160|    -     0|    -      0
  src/adapters/ethers/resources/contracts/contracts.ts                        | 0.0%     37|    -     0|    -      0
  src/adapters/ethers/resources/interop/address.ts                            | 0.0%     17|    -     0|    -      0
  src/adapters/ethers/resources/interop/attributes/resource.ts                | 0.0%     35|    -     0|    -      0
  src/adapters/ethers/resources/interop/context.ts                            | 0.0%     77|    -     0|    -      0
  src/adapters/ethers/resources/interop/index.ts                              | 0.0%    160|    -     0|    -      0
  src/adapters/ethers/resources/interop/resolvers.ts                          | 0.0%     10|    -     0|    -      0
  src/adapters/ethers/resources/interop/routes/direct.ts                      | 0.0%     50|    -     0|    -      0
  src/adapters/ethers/resources/interop/routes/indirect.ts                    | 0.0%     82|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/erc20.ts                     | 0.0%     37|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/bundle.ts       | 0.0%     10|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/data-fetchers.ts| 0.0%     22|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/decoders.ts     | 0.0%      2|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/index.ts        | 0.0%     11|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/polling.ts      | 0.0%     12|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/status.ts       | 0.0%     29|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/finalization/topics.ts       | 0.0%     13|    -     0|    -      0
  src/adapters/ethers/resources/interop/services/starter-data.ts              | 0.0%     41|    -     0|    -      0
  src/adapters/ethers/resources/withdrawals/services/finalization.ts          | 0.0%    153|    -     0|    -      0
  src/adapters/ethers/sdk.ts                                                  | 0.0%     16|    -     0|    -      0
  src/adapters/viem/client.ts                                                 | 0.0%    116|    -     0|    -      0
  src/adapters/viem/resources/withdrawals/services/finalization.ts            | 0.0%    175|    -     0|    -      0
  src/core/resources/interop/attributes/bundle.ts                             | 0.0%      7|    -     0|    -      0
  src/core/resources/interop/finalization.ts                                  | 0.0%    144|    -     0|    -      0
  src/core/resources/interop/plan.ts                                          | 0.0%    135|    -     0|    -      0
  src/core/types/errors.ts                                                    | 0.0%    184|    -     0|    -      0
  src/core/types/flows/interop.ts                                             | 0.0%     28|    -     0|    -      0
  src/core/types/primitives.ts                                                | 0.0%      1|    -     0|    -      0

Full coverage report

Copy link
Contributor

@dutterbutter dutterbutter left a comment

Choose a reason for hiding this comment

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

Thoughts on re-organizing services/finalization to be more similar to withdrawals approach? That is, introducing createInteropFinalizationServices(client) (same pattern as withdrawals) and delegating status/wait/finalize through that service.

So services/finalization/index.ts with the interface:

// src/adapters/ethers/resources/interop/services/finalization/index.ts

export interface InteropFinalizationServices {
  status(input: InteropWaitable): Promise<InteropStatus>;
  wait(
    input: InteropWaitable,
    opts?: { pollMs?: number; timeoutMs?: number },
  ): Promise<InteropFinalizationInfo>;
  finalize(info: InteropFinalizationInfo): Promise<InteropFinalizationResult>;
}

This keeps the resources more consistent, centralizes finalization concerns (topics/decoding/polling/execute).

@dutterbutter dutterbutter merged commit 4635438 into main Feb 13, 2026
15 checks passed
@dutterbutter dutterbutter deleted the vi/ethers-adapter-interop branch February 13, 2026 18:59
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.

2 participants