Skip to content

feat(sdk): add confidentialTransferAndCall to Token methods [SDK-168]#423

Open
ghermet wants to merge 3 commits into
prereleasefrom
feature/sdk-168-feat-add-transferandcall-to-base-token-methods
Open

feat(sdk): add confidentialTransferAndCall to Token methods [SDK-168]#423
ghermet wants to merge 3 commits into
prereleasefrom
feature/sdk-168-feat-add-transferandcall-to-base-token-methods

Conversation

@ghermet

@ghermet ghermet commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes SDK-168.

Adds Token.confidentialTransferAndCall and Token.confidentialTransferFromAndCall on the ERC-7984 confidential-token surface so callers can invoke a recipient's receiver hook (onConfidentialTransferReceived) in a single transaction. The caller crafts the opaque data payload; the SDK does not encode, validate, or interpret it.

This is the confidential-token level (ERC-7984), distinct from the shielding transferAndCall work in SDK-144/145 (ERC-1363 on the underlying).

Changes

  • packages/sdk/src/contracts/encrypted.tsconfidentialTransferAndCallContract and confidentialTransferFromAndCallContract pure builders.
  • packages/sdk/src/token/token.tsToken.confidentialTransferAndCall(to, amount, data, options?) and Token.confidentialTransferFromAndCall(from, to, amount, data, callbacks?), mirroring the non-AndCall variants' balance validation, encryption, and callback semantics.
  • packages/sdk/src/events/sdk-events.ts — new transferAndCall / transferFromAndCall operations (reuse TransferSubmitted / TransferFromSubmitted event shapes for clean error attribution).
  • packages/sdk/src/query/transfer-and-call.ts + transfer-from-and-call.ts — TanStack mutation options factories.
  • packages/react-sdk/src/transfer/use-confidential-transfer-and-call.ts + use-confidential-transfer-from-and-call.ts — hooks (with optimistic balance for the sender variant).

Tests

  • Unit tests in packages/sdk/src/contracts/__tests__/contracts.test.ts and packages/sdk/src/token/__tests__/token.test.ts (10 new tests) — data forwarding, balance validation, error mapping.
  • Browser Playwright e2e (test/playwright/tests/transfer-and-call.spec.ts) — drives useConfidentialTransferAndCall against OpenZeppelin's ERC7984ReceiverMock, deployed via contracts/script/Deploy.s.sol. Asserts both the success path (abi.encode(uint8(1)) → confidential balance decreases) and a revert path (abi.encode(uint8(2))InvalidInput surfaces as a transfer error). Passes on Vite and Next.js.

Test plan

  • pnpm typecheck clean
  • pnpm lint clean
  • pnpm test:run — 1504 passing (10 new unit tests)
  • pnpm e2e:test:vite --grep transferAndCall — 2/2 pass
  • pnpm e2e:test:nextjs --grep transferAndCall — 2/2 pass

🤖 Generated with Claude Code

ghermet and others added 2 commits June 16, 2026 08:45
Adds confidentialTransferAndCall and confidentialTransferFromAndCall on
the ERC-7984 confidential-token surface so callers can invoke a recipient's
receiver hook (onConfidentialTransferReceived) in a single transaction.
The caller crafts the opaque data payload; the SDK does not encode,
validate, or interpret it.

- contracts/encrypted.ts: confidentialTransferAndCallContract,
  confidentialTransferFromAndCallContract pure builders
- token.ts: Token.confidentialTransferAndCall / FromAndCall mirroring the
  existing confidentialTransfer / FromAndCall, with the same balance
  validation, encryption, and callback semantics
- sdk-events: transferAndCall / transferFromAndCall operation strings for
  clean error attribution (events reuse TransferSubmitted / FromSubmitted
  shapes)
- query/: confidentialTransferAndCallMutationOptions and
  confidentialTransferFromAndCallMutationOptions
- react-sdk: useConfidentialTransferAndCall (with optimistic balance) and
  useConfidentialTransferFromAndCall hooks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… [SDK-168]

Deploys OpenZeppelin's ERC7984ReceiverMock (already vendored under the
wrapper deps) alongside the existing test tokens and drives
useConfidentialTransferAndCall end-to-end from both the Vite and Next.js
test apps.

- contracts: deploy ERC7984ReceiverMock and expose its address as
  `confidentialReceiver` in deployments.json
- test-components: TransferAndCallForm with recipient/amount/data inputs
- test-nextjs / test-vite: /transfer-and-call route + sidebar entry
- playwright: transfer-and-call.spec.ts asserts a success path
  (abi.encode(uint8(1)) → ConfidentialTransferCallback(true), balance
  decreases) and a revert path (abi.encode(uint8(2)) → InvalidInput,
  surfaces a transfer error)
- playwright fixtures: expose `confidentialReceiver` from deployments

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label Jun 16, 2026
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 91.46% (🎯 80%) 3128 / 3420
🔵 Statements 91.56% 3224 / 3521
🔵 Functions 91.61% (🎯 80%) 1049 / 1145
🔵 Branches 84.45% (🎯 80%) 1201 / 1422
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react-sdk/src/transfer/use-confidential-transfer-and-call.ts 0% 0% 0% 0% 1-110
packages/react-sdk/src/transfer/use-confidential-transfer-from-and-call.ts 0% 100% 0% 0% 1-55
packages/sdk/src/contracts/encrypted.ts 100% 100% 100% 100%
packages/sdk/src/events/sdk-events.ts 100% 100% 100% 100%
packages/sdk/src/query/transfer-and-call.ts 0% 100% 0% 0% 21-24
packages/sdk/src/query/transfer-from-and-call.ts 0% 100% 0% 0% 24-27
packages/sdk/src/token/token.ts 88.2% 69.76% 100% 87.7% 165, 267, 285, 297-309, 315-316, 363-365, 378, 402, 410-416, 470, 480, 486-496, 605, 741, 889-891
Generated in workflow #2568 for commit 7a5add8 by the Vitest Coverage Report Action

Regenerates the api-report files to expose the new
`confidentialTransferAndCall` / `confidentialTransferFromAndCall`
methods on `Token`, their query params, and the matching
`useConfidentialTransferAndCall` / `useConfidentialTransferFromAndCall`
React hooks added in b6c38d2. CI api-report check was the only red
gate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Public API Changes

react-sdk.api.md
--- a/react-sdk.api.md
+++ b/react-sdk.api.md
@@ -12,6 +12,8 @@
 import { ClearValues } from '@zama-fhe/relayer-sdk/web';
 import { ClearValueType } from '@zama-fhe/relayer-sdk/web';
 import { ConfidentialSetOperatorParams } from '@zama-fhe/sdk/query';
+import { ConfidentialTransferAndCallParams } from '@zama-fhe/sdk/query';
+import { ConfidentialTransferFromAndCallParams } from '@zama-fhe/sdk/query';
 import { ConfidentialTransferFromParams } from '@zama-fhe/sdk/query';
 import { ConfidentialTransferParams } from '@zama-fhe/sdk/query';
 import { DecryptBalanceAsParams } from '@zama-fhe/sdk/query';
@@ -115,6 +117,15 @@
 export function useConfidentialTransfer<TContext = unknown>(config: UseConfidentialTransferConfig, options?: UseMutationOptions<TransactionResult, Error, ConfidentialTransferParams, TContext>): UseMutationResult<TransactionResult, Error, ConfidentialTransferParams, TContext>;
 
 // @public
+export function useConfidentialTransferAndCall<TContext = unknown>(config: UseConfidentialTransferAndCallConfig, options?: UseMutationOptions<TransactionResult, Error, ConfidentialTransferAndCallParams, TContext>): UseMutationResult<TransactionResult, Error, ConfidentialTransferAndCallParams, TContext>;
+
+// @public
+export interface UseConfidentialTransferAndCallConfig {
+    address: Address;
+    optimistic?: boolean;
+}
+
+// @public
 export interface UseConfidentialTransferConfig {
     address: Address;
     optimistic?: boolean;
@@ -124,6 +135,9 @@
 export function useConfidentialTransferFrom(address: Address, options?: UseMutationOptions<TransactionResult, Error, ConfidentialTransferFromParams, Address>): UseMutationResult<TransactionResult, Error, ConfidentialTransferFromParams, `0x${string}`>;
 
 // @public
+export function useConfidentialTransferFromAndCall(address: Address, options?: UseMutationOptions<TransactionResult, Error, ConfidentialTransferFromAndCallParams, Address>): UseMutationResult<TransactionResult, Error, ConfidentialTransferFromAndCallParams, `0x${string}`>;
+
+// @public
 export function useDecryptBalanceAs(address: Address, options?: UseMutationOptions<bigint, Error, DecryptBalanceAsParams>): UseMutationResult<bigint, Error, DecryptBalanceAsParams, unknown>;
 
 // @public
sdk-query.api.md
--- a/sdk-query.api.md
+++ b/sdk-query.api.md
@@ -142,6 +142,18 @@
 // @public (undocumented)
 export function confidentialTokenAddressQueryOptions(sdk: ZamaSDK, config: ConfidentialTokenAddressQueryConfig): QueryFactoryOptions<readonly [boolean, Address], Error, readonly [boolean, Address], ReturnType<typeof zamaQueryKeys.wrappersRegistry.confidentialTokenAddress>>;
 
+// @public (undocumented)
+export function confidentialTransferAndCallMutationOptions(token: Token): MutationFactoryOptions<readonly ["zama.confidentialTransferAndCall", Address], ConfidentialTransferAndCallParams, TransactionResult>;
+
+// @public
+export interface ConfidentialTransferAndCallParams extends TransferOptions {
+    // (undocumented)
+    amount: bigint;
+    data: Hex;
+    // (undocumented)
+    to: Address;
+}
+
 // @public
 export interface ConfidentialTransferEvent {
     readonly encryptedAmount: EncryptedValue;
@@ -152,6 +164,21 @@
 }
 
 // @public (undocumented)
+export function confidentialTransferFromAndCallMutationOptions(token: Token): MutationFactoryOptions<readonly ["zama.confidentialTransferFromAndCall", Address], ConfidentialTransferFromAndCallParams, TransactionResult>;
+
+// @public
+export interface ConfidentialTransferFromAndCallParams {
+    // (undocumented)
+    amount: bigint;
+    callbacks?: TransferCallbacks;
+    data: Hex;
+    // (undocumented)
+    from: Address;
+    // (undocumented)
+    to: Address;
+}
+
+// @public (undocumented)
 export function confidentialTransferFromMutationOptions(token: Token): MutationFactoryOptions<readonly ["zama.confidentialTransferFrom", Address], ConfidentialTransferFromParams, TransactionResult>;
 
 // @public
@@ -664,7 +691,9 @@
     static batchDecryptBalancesAs(tokens: Token[], options: BatchDecryptAsOptions): Promise<Map<Address, bigint>>;
     confidentialBalanceOf(owner: Address): Promise<EncryptedValue>;
     confidentialTransfer(to: Address, amount: bigint, options?: TransferOptions): Promise<TransactionResult>;
+    confidentialTransferAndCall(to: Address, amount: bigint, data: Hex, options?: TransferOptions): Promise<TransactionResult>;
     confidentialTransferFrom(from: Address, to: Address, amount: bigint, callbacks?: TransferCallbacks): Promise<TransactionResult>;
+    confidentialTransferFromAndCall(from: Address, to: Address, amount: bigint, data: Hex, callbacks?: TransferCallbacks): Promise<TransactionResult>;
     decimals(): Promise<number>;
     decryptBalanceAs(input: {
         delegatorAddress: Address;
sdk.api.md
--- a/sdk.api.md
+++ b/sdk.api.md
@@ -14835,7 +14835,9 @@
     static batchDecryptBalancesAs(tokens: Token[], options: BatchDecryptAsOptions): Promise<Map<Address, bigint>>;
     confidentialBalanceOf(owner: Address): Promise<EncryptedValue>;
     confidentialTransfer(to: Address, amount: bigint, options?: TransferOptions): Promise<TransactionResult>;
+    confidentialTransferAndCall(to: Address, amount: bigint, data: Hex, options?: TransferOptions): Promise<TransactionResult>;
     confidentialTransferFrom(from: Address, to: Address, amount: bigint, callbacks?: TransferCallbacks): Promise<TransactionResult>;
+    confidentialTransferFromAndCall(from: Address, to: Address, amount: bigint, data: Hex, callbacks?: TransferCallbacks): Promise<TransactionResult>;
     decimals(): Promise<number>;
     decryptBalanceAs(input: {
         delegatorAddress: Address;

@ankurdotb ankurdotb added the do not merge This is not ready to be merged, waiting on someone else's work label Jun 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed do not merge This is not ready to be merged, waiting on someone else's work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants