@circle-fin/adapter-ethers-v6 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @circle-fin/adapter-ethers-v6
2
2
 
3
+ ## 1.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `native.balanceOf` action to query native token balances (ETH, SOL, etc.)
8
+ - Added `NativeActionMap` with `balanceOf` action to check native token balance for any wallet address
9
+ - Added abstract `readNativeBalance(address, chain)` method to base adapters
10
+ - Implemented `readNativeBalance` in all concrete adapters (Viem, Ethers, Solana, SolanaKit)
11
+ - Registered `native.balanceOf` action handlers for EVM and Solana chains
12
+ - Balance reads are gas-free operations returning balance as string (wei for EVM, lamports for Solana)
13
+
14
+ ### Patch Changes
15
+
16
+ - **Faster balance and allowance checks**: Read-only operations like checking token balances or allowances no longer require wallet network switching. This means no wallet popups asking for permission to switch networks, resulting in quicker responses and a smoother user experience.
17
+
3
18
  ## 1.2.0
4
19
 
5
20
  ### Minor Changes
package/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Copyright (c) 2025, Circle Internet Group, Inc. All rights reserved.
2
+ * Copyright (c) 2026, Circle Internet Group, Inc. All rights reserved.
3
3
  *
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  *
@@ -623,7 +623,7 @@ function createValidationErrorFromZod(zodError, context) {
623
623
  *
624
624
  * @param chain - The blockchain network where the balance check failed
625
625
  * @param token - The token symbol (e.g., 'USDC', 'ETH')
626
- * @param rawError - The original error from the underlying system (optional)
626
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
627
627
  * @returns KitError with insufficient token balance details
628
628
  *
629
629
  * @example
@@ -636,24 +636,28 @@ function createValidationErrorFromZod(zodError, context) {
636
636
  *
637
637
  * @example
638
638
  * ```typescript
639
- * // With raw error for debugging
639
+ * // With trace context for debugging
640
640
  * try {
641
641
  * await transfer(...)
642
642
  * } catch (error) {
643
- * throw createInsufficientTokenBalanceError('Base', 'USDC', error)
643
+ * throw createInsufficientTokenBalanceError('Base', 'USDC', {
644
+ * rawError: error,
645
+ * balance: '1000000',
646
+ * amount: '5000000',
647
+ * })
644
648
  * }
645
649
  * ```
646
650
  */
647
- function createInsufficientTokenBalanceError(chain, token, rawError) {
651
+ function createInsufficientTokenBalanceError(chain, token, trace) {
648
652
  return new KitError({
649
653
  ...BalanceError.INSUFFICIENT_TOKEN,
650
654
  recoverability: 'FATAL',
651
655
  message: `Insufficient ${token} balance on ${chain}`,
652
656
  cause: {
653
657
  trace: {
658
+ ...trace,
654
659
  chain,
655
660
  token,
656
- rawError,
657
661
  },
658
662
  },
659
663
  });
@@ -666,7 +670,7 @@ function createInsufficientTokenBalanceError(chain, token, rawError) {
666
670
  * as it requires user intervention to add gas funds.
667
671
  *
668
672
  * @param chain - The blockchain network where the gas check failed
669
- * @param rawError - The original error from the underlying system (optional)
673
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
670
674
  * @returns KitError with insufficient gas details
671
675
  *
672
676
  * @example
@@ -676,16 +680,27 @@ function createInsufficientTokenBalanceError(chain, token, rawError) {
676
680
  * throw createInsufficientGasError('Ethereum')
677
681
  * // Message: "Insufficient gas funds on Ethereum"
678
682
  * ```
683
+ *
684
+ * @example
685
+ * ```typescript
686
+ * // With trace context for debugging
687
+ * throw createInsufficientGasError('Ethereum', {
688
+ * rawError: error,
689
+ * gasRequired: '21000',
690
+ * gasAvailable: '10000',
691
+ * walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
692
+ * })
693
+ * ```
679
694
  */
680
- function createInsufficientGasError(chain, rawError) {
695
+ function createInsufficientGasError(chain, trace) {
681
696
  return new KitError({
682
697
  ...BalanceError.INSUFFICIENT_GAS,
683
698
  recoverability: 'FATAL',
684
699
  message: `Insufficient gas funds on ${chain}`,
685
700
  cause: {
686
701
  trace: {
702
+ ...trace,
687
703
  chain,
688
- rawError,
689
704
  },
690
705
  },
691
706
  });
@@ -700,7 +715,7 @@ function createInsufficientGasError(chain, rawError) {
700
715
  *
701
716
  * @param chain - The blockchain network where the simulation failed
702
717
  * @param reason - The reason for simulation failure (e.g., revert message)
703
- * @param rawError - The original error from the underlying system (optional)
718
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
704
719
  * @returns KitError with simulation failure details
705
720
  *
706
721
  * @example
@@ -710,17 +725,27 @@ function createInsufficientGasError(chain, rawError) {
710
725
  * throw createSimulationFailedError('Ethereum', 'ERC20: insufficient allowance')
711
726
  * // Message: "Simulation failed on Ethereum: ERC20: insufficient allowance"
712
727
  * ```
728
+ *
729
+ * @example
730
+ * ```typescript
731
+ * // With trace context for debugging
732
+ * throw createSimulationFailedError('Ethereum', 'ERC20: insufficient allowance', {
733
+ * rawError: error,
734
+ * txHash: '0x1234...',
735
+ * gasLimit: '21000',
736
+ * })
737
+ * ```
713
738
  */
714
- function createSimulationFailedError(chain, reason, rawError) {
739
+ function createSimulationFailedError(chain, reason, trace) {
715
740
  return new KitError({
716
741
  ...OnchainError.SIMULATION_FAILED,
717
742
  recoverability: 'FATAL',
718
743
  message: `Simulation failed on ${chain}: ${reason}`,
719
744
  cause: {
720
745
  trace: {
746
+ ...trace,
721
747
  chain,
722
748
  reason,
723
- rawError,
724
749
  },
725
750
  },
726
751
  });
@@ -734,7 +759,7 @@ function createSimulationFailedError(chain, reason, rawError) {
734
759
  *
735
760
  * @param chain - The blockchain network where the transaction reverted
736
761
  * @param reason - The reason for the revert (e.g., revert message)
737
- * @param rawError - The original error from the underlying system (optional)
762
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
738
763
  * @returns KitError with transaction revert details
739
764
  *
740
765
  * @example
@@ -744,17 +769,27 @@ function createSimulationFailedError(chain, reason, rawError) {
744
769
  * throw createTransactionRevertedError('Base', 'Slippage exceeded')
745
770
  * // Message: "Transaction reverted on Base: Slippage exceeded"
746
771
  * ```
772
+ *
773
+ * @example
774
+ * ```typescript
775
+ * // With trace context for debugging
776
+ * throw createTransactionRevertedError('Base', 'Slippage exceeded', {
777
+ * rawError: error,
778
+ * txHash: '0xabc...',
779
+ * blockNumber: '12345',
780
+ * })
781
+ * ```
747
782
  */
748
- function createTransactionRevertedError(chain, reason, rawError) {
783
+ function createTransactionRevertedError(chain, reason, trace) {
749
784
  return new KitError({
750
785
  ...OnchainError.TRANSACTION_REVERTED,
751
786
  recoverability: 'FATAL',
752
787
  message: `Transaction reverted on ${chain}: ${reason}`,
753
788
  cause: {
754
789
  trace: {
790
+ ...trace,
755
791
  chain,
756
792
  reason,
757
- rawError,
758
793
  },
759
794
  },
760
795
  });
@@ -766,7 +801,7 @@ function createTransactionRevertedError(chain, reason, rawError) {
766
801
  * The error is FATAL as it requires adjusting gas limits or transaction logic.
767
802
  *
768
803
  * @param chain - The blockchain network where the transaction ran out of gas
769
- * @param rawError - The original error from the underlying system (optional)
804
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
770
805
  * @returns KitError with out of gas details
771
806
  *
772
807
  * @example
@@ -776,16 +811,26 @@ function createTransactionRevertedError(chain, reason, rawError) {
776
811
  * throw createOutOfGasError('Polygon')
777
812
  * // Message: "Transaction ran out of gas on Polygon"
778
813
  * ```
814
+ *
815
+ * @example
816
+ * ```typescript
817
+ * // With trace context for debugging
818
+ * throw createOutOfGasError('Polygon', {
819
+ * rawError: error,
820
+ * gasUsed: '50000',
821
+ * gasLimit: '45000',
822
+ * })
823
+ * ```
779
824
  */
780
- function createOutOfGasError(chain, rawError) {
825
+ function createOutOfGasError(chain, trace) {
781
826
  return new KitError({
782
827
  ...OnchainError.OUT_OF_GAS,
783
828
  recoverability: 'FATAL',
784
829
  message: `Transaction ran out of gas on ${chain}`,
785
830
  cause: {
786
831
  trace: {
832
+ ...trace,
787
833
  chain,
788
- rawError,
789
834
  },
790
835
  },
791
836
  });
@@ -798,7 +843,7 @@ function createOutOfGasError(chain, rawError) {
798
843
  * or is unavailable. The error is RETRYABLE as RPC issues are often temporary.
799
844
  *
800
845
  * @param chain - The blockchain network where the RPC error occurred
801
- * @param rawError - The original error from the underlying system (optional)
846
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
802
847
  * @returns KitError with RPC endpoint error details
803
848
  *
804
849
  * @example
@@ -808,16 +853,26 @@ function createOutOfGasError(chain, rawError) {
808
853
  * throw createRpcEndpointError('Ethereum')
809
854
  * // Message: "RPC endpoint error on Ethereum"
810
855
  * ```
856
+ *
857
+ * @example
858
+ * ```typescript
859
+ * // With trace context for debugging
860
+ * throw createRpcEndpointError('Ethereum', {
861
+ * rawError: error,
862
+ * endpoint: 'https://mainnet.infura.io/v3/...',
863
+ * statusCode: 429,
864
+ * })
865
+ * ```
811
866
  */
812
- function createRpcEndpointError(chain, rawError) {
867
+ function createRpcEndpointError(chain, trace) {
813
868
  return new KitError({
814
869
  ...RpcError.ENDPOINT_ERROR,
815
870
  recoverability: 'RETRYABLE',
816
871
  message: `RPC endpoint error on ${chain}`,
817
872
  cause: {
818
873
  trace: {
874
+ ...trace,
819
875
  chain,
820
- rawError,
821
876
  },
822
877
  },
823
878
  });
@@ -831,7 +886,7 @@ function createRpcEndpointError(chain, rawError) {
831
886
  * often temporary.
832
887
  *
833
888
  * @param chain - The blockchain network where the connection failed
834
- * @param rawError - The original error from the underlying system (optional)
889
+ * @param trace - Optional trace context to include in error (can include rawError and additional debugging data)
835
890
  * @returns KitError with network connection error details
836
891
  *
837
892
  * @example
@@ -841,16 +896,26 @@ function createRpcEndpointError(chain, rawError) {
841
896
  * throw createNetworkConnectionError('Ethereum')
842
897
  * // Message: "Network connection failed for Ethereum"
843
898
  * ```
899
+ *
900
+ * @example
901
+ * ```typescript
902
+ * // With trace context for debugging
903
+ * throw createNetworkConnectionError('Ethereum', {
904
+ * rawError: error,
905
+ * endpoint: 'https://eth-mainnet.g.alchemy.com/v2/...',
906
+ * retryCount: 3,
907
+ * })
908
+ * ```
844
909
  */
845
- function createNetworkConnectionError(chain, rawError) {
910
+ function createNetworkConnectionError(chain, trace) {
846
911
  return new KitError({
847
912
  ...NetworkError.CONNECTION_FAILED,
848
913
  recoverability: 'RETRYABLE',
849
914
  message: `Network connection failed for ${chain}`,
850
915
  cause: {
851
916
  trace: {
917
+ ...trace,
852
918
  chain,
853
- rawError,
854
919
  },
855
920
  },
856
921
  });
@@ -905,7 +970,9 @@ function parseBlockchainError(error, context) {
905
970
  // Pattern 1: Insufficient balance errors
906
971
  // Matches balance-related errors from ERC20 contracts, native transfers, and Solana programs
907
972
  if (/transfer amount exceeds balance|insufficient (balance|funds)|burn amount exceeded/i.test(msg)) {
908
- return createInsufficientTokenBalanceError(context.chain, token, error);
973
+ return createInsufficientTokenBalanceError(context.chain, token, {
974
+ rawError: error,
975
+ });
909
976
  }
910
977
  // Pattern 2: Simulation and execution reverts
911
978
  // Matches contract revert errors and simulation failures
@@ -915,46 +982,50 @@ function parseBlockchainError(error, context) {
915
982
  // "simulation failed" or "eth_call" indicates pre-flight simulation
916
983
  // "transaction failed" or context.operation === 'transaction' indicates post-execution
917
984
  if (/simulation failed/i.test(msg) || context.operation === 'simulation') {
918
- return createSimulationFailedError(context.chain, reason, error);
985
+ return createSimulationFailedError(context.chain, reason, {
986
+ rawError: error,
987
+ });
919
988
  }
920
989
  // Transaction execution failures or reverts
921
- return createTransactionRevertedError(context.chain, reason, error);
990
+ return createTransactionRevertedError(context.chain, reason, {
991
+ rawError: error,
992
+ });
922
993
  }
923
994
  // Pattern 3: Gas-related errors
924
995
  // Matches gas estimation failures and gas exhaustion
925
996
  // Check specific patterns first, then generic "gas" patterns
926
997
  // Gas estimation failures are RPC issues
927
998
  if (/gas estimation failed|cannot estimate gas/i.test(msg)) {
928
- return createRpcEndpointError(context.chain, error);
999
+ return createRpcEndpointError(context.chain, { rawError: error });
929
1000
  }
930
1001
  // Gas exhaustion errors
931
1002
  // Use specific patterns without wildcards to avoid ReDoS
932
1003
  if (/out of gas|gas limit exceeded|exceeds block gas limit/i.test(msg)) {
933
- return createOutOfGasError(context.chain, error);
1004
+ return createOutOfGasError(context.chain, { rawError: error });
934
1005
  }
935
1006
  // Insufficient funds for gas
936
1007
  if (/insufficient funds for gas/i.test(msg)) {
937
- return createInsufficientGasError(context.chain, error);
1008
+ return createInsufficientGasError(context.chain, { rawError: error });
938
1009
  }
939
1010
  // Pattern 4: Network connectivity errors
940
1011
  // Matches connection failures, DNS errors, and timeouts
941
1012
  if (/connection (refused|failed)|network|timeout|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
942
- return createNetworkConnectionError(context.chain, error);
1013
+ return createNetworkConnectionError(context.chain, { rawError: error });
943
1014
  }
944
1015
  // Pattern 5: RPC provider errors
945
1016
  // Matches RPC endpoint errors, invalid responses, and rate limits
946
1017
  if (/rpc|invalid response|rate limit|too many requests/i.test(msg)) {
947
- return createRpcEndpointError(context.chain, error);
1018
+ return createRpcEndpointError(context.chain, { rawError: error });
948
1019
  }
949
1020
  // Fallback based on operation context
950
1021
  // Gas-related operations are RPC calls
951
1022
  if (context.operation === 'estimateGas' ||
952
1023
  context.operation === 'getGasPrice') {
953
- return createRpcEndpointError(context.chain, error);
1024
+ return createRpcEndpointError(context.chain, { rawError: error });
954
1025
  }
955
1026
  // Fallback for unrecognized errors
956
1027
  // Defaults to simulation failed as transaction execution is the most common failure point
957
- return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Unknown error', error);
1028
+ return createSimulationFailedError(context.chain, msg.length > 0 ? msg : 'Unknown error', { rawError: error });
958
1029
  }
959
1030
  /**
960
1031
  * Type guard to check if error has Solana-Kit structure with logs.
@@ -1401,8 +1472,11 @@ const ArcTestnet = defineChain({
1401
1472
  name: 'Arc Testnet',
1402
1473
  title: 'ArcTestnet',
1403
1474
  nativeCurrency: {
1404
- name: 'Arc',
1405
- symbol: 'Arc',
1475
+ name: 'USDC',
1476
+ symbol: 'USDC',
1477
+ // Arc uses native USDC with 18 decimals for gas payments (EVM standard).
1478
+ // Note: The ERC-20 USDC contract at usdcAddress uses 6 decimals.
1479
+ // See: https://docs.arc.network/arc/references/contract-addresses
1406
1480
  decimals: 18,
1407
1481
  },
1408
1482
  chainId: 5042002,
@@ -8506,6 +8580,60 @@ const customBurn = async (params, adapter, context) => {
8506
8580
  }, context);
8507
8581
  };
8508
8582
 
8583
+ /**
8584
+ * Prepares an EVM-compatible native token balance read (ETH, MATIC, etc.).
8585
+ *
8586
+ * This function creates a prepared chain request for reading the native token balance
8587
+ * of a given wallet address on an EVM-based chain. If no wallet address is provided,
8588
+ * the adapter's default address from context is used.
8589
+ *
8590
+ * @param params - The action payload for the native balance lookup:
8591
+ * - `walletAddress` (optional): The wallet address to check. Defaults to context address.
8592
+ * @param adapter - The EVM adapter providing chain context.
8593
+ * @param context - The resolved operation context providing chain and address information.
8594
+ * @returns A promise resolving to a prepared chain request.
8595
+ * The `execute` method returns the balance as a string (in wei).
8596
+ * @throws {KitError} If the current chain is not EVM-compatible (INPUT_INVALID_CHAIN).
8597
+ * @throws Error If address validation fails.
8598
+ *
8599
+ * @example
8600
+ * ```typescript
8601
+ * import { Ethereum } from '@core/chains'
8602
+ *
8603
+ * const prepared = await balanceOf(
8604
+ * { walletAddress: '0x1234...' },
8605
+ * adapter,
8606
+ * context
8607
+ * )
8608
+ * const balance = await prepared.execute()
8609
+ * console.log(balance) // e.g., "1000000000000000000" (1 ETH in wei)
8610
+ *
8611
+ * // Check balance for the adapter's address (no walletAddress provided)
8612
+ * const prepared2 = await balanceOf({}, adapter, context)
8613
+ * const balance2 = await prepared2.execute()
8614
+ * ```
8615
+ */
8616
+ const balanceOf$2 = async (params, adapter, context) => {
8617
+ const chain = context.chain;
8618
+ if (chain.type !== 'evm') {
8619
+ throw createInvalidChainError(chain.name, `Expected EVM chain definition, but received chain type: ${chain.type}`);
8620
+ }
8621
+ // Use provided wallet address or fall back to context address
8622
+ const walletAddress = params.walletAddress ?? context.address;
8623
+ // Validate the address
8624
+ assertEvmAddress(walletAddress);
8625
+ // Create noop request for gas estimation (reading balance is free)
8626
+ const noopRequest = await createNoopChainRequest();
8627
+ return {
8628
+ type: 'evm',
8629
+ estimate: noopRequest.estimate,
8630
+ execute: async () => {
8631
+ const balance = await adapter.readNativeBalance(walletAddress, chain);
8632
+ return balance.toString();
8633
+ },
8634
+ };
8635
+ };
8636
+
8509
8637
  /**
8510
8638
  * Prepares an EVM-compatible `balanceOf` read for any ERC-20 token.
8511
8639
  *
@@ -8832,6 +8960,14 @@ const getHandlers = (adapter) => {
8832
8960
  'cctp.v2.customBurn': async (params, context) => {
8833
8961
  return customBurn(params, adapter, context);
8834
8962
  },
8963
+ /**
8964
+ * Handler for native token balance operations on EVM chains.
8965
+ *
8966
+ * Gets the native balance (ETH, MATIC, etc.) for the given address.
8967
+ */
8968
+ 'native.balanceOf': async (params, context) => {
8969
+ return balanceOf$2(params, adapter, context);
8970
+ },
8835
8971
  };
8836
8972
  };
8837
8973
 
@@ -10845,18 +10981,13 @@ class EthersAdapter extends EvmAdapter {
10845
10981
  return this._signer;
10846
10982
  }
10847
10983
  /**
10848
- * Creates a contract instance for the given address, ABI, and wallet.
10984
+ * Parses and validates an ABI, ensuring the specified function exists.
10985
+ *
10986
+ * @param abi - The ABI to parse (can be a string array or object array).
10987
+ * @param functionName - The function name to validate exists in the ABI.
10988
+ * @throws Error when ABI parsing fails or function is not found.
10849
10989
  */
10850
- createContractInstance(params, signer, walletAddress, provider) {
10851
- const { address, abi: abiInput, functionName } = params;
10852
- // Wallet address is passed as parameter to avoid race conditions in concurrent requests
10853
- if (!walletAddress) {
10854
- throw new Error('Unable to retrieve account from signer. Please ensure a valid wallet is connected.');
10855
- }
10856
- assertEvmPreparedChainRequestParams(params);
10857
- const abi = Array.isArray(abiInput) && typeof abiInput[0] === 'string'
10858
- ? abiInput
10859
- : abiInput;
10990
+ _parseAndValidateAbi(abi, functionName) {
10860
10991
  let contractInterface = undefined;
10861
10992
  try {
10862
10993
  contractInterface = new ethers.Interface(abi);
@@ -10871,6 +11002,21 @@ class EthersAdapter extends EvmAdapter {
10871
11002
  if (!functionNames.includes(functionName)) {
10872
11003
  throw new Error(`Function '${functionName}' not found in ABI.`);
10873
11004
  }
11005
+ }
11006
+ /**
11007
+ * Creates a contract instance for the given address, ABI, and wallet.
11008
+ */
11009
+ createContractInstance(params, signer, walletAddress, provider) {
11010
+ const { address, abi: abiInput, functionName } = params;
11011
+ // Wallet address is passed as parameter to avoid race conditions in concurrent requests
11012
+ if (!walletAddress) {
11013
+ throw new Error('Unable to retrieve account from signer. Please ensure a valid wallet is connected.');
11014
+ }
11015
+ assertEvmPreparedChainRequestParams(params);
11016
+ const abi = Array.isArray(abiInput) && typeof abiInput[0] === 'string'
11017
+ ? abiInput
11018
+ : abiInput;
11019
+ this._parseAndValidateAbi(abi, functionName);
10874
11020
  const contract = provider
10875
11021
  ? new ethers.Contract(address, abi, provider).connect(signer)
10876
11022
  : new ethers.Contract(address, abi, signer);
@@ -11131,27 +11277,24 @@ class EthersAdapter extends EvmAdapter {
11131
11277
  if (targetChain.type !== 'evm') {
11132
11278
  throw new Error(`Invalid chain type '${String(targetChain.type)}' for EthersAdapter. Expected 'evm' chain type.`);
11133
11279
  }
11134
- // Ensure we're on the correct chain BEFORE resolving context
11135
- // This is critical because resolveOperationContext may call getAddress(),
11136
- // which queries the wallet. For browser wallets, we must be on the correct
11137
- // chain before querying wallet state (especially for smart contract wallets).
11138
- await this.ensureChain(targetChain);
11139
- // Now resolve the full operation context (address resolution happens on correct chain)
11140
- const resolvedContext = await resolveOperationContext(this, ctx);
11141
- if (!resolvedContext) {
11142
- throw new Error('OperationContext resolution failed. Ensure the adapter has capabilities configured.');
11143
- }
11144
- const signer = this.getSigner();
11145
- const provider = await this.getProvider(targetChain);
11146
11280
  // Check if the function is read-only (view or pure)
11281
+ // Read-only functions don't need chain switching
11147
11282
  // If function is not found in ABI or ABI is invalid, default to false
11148
11283
  const isReadOnly = Array.isArray(abi)
11149
11284
  ? isReadOnlyFunction(abi, functionName, false)
11150
11285
  : false;
11286
+ const provider = await this.getProvider(targetChain);
11151
11287
  if (isReadOnly) {
11152
- return this.handleReadOnlyFunction(params, signer, provider, resolvedContext.address);
11288
+ return this.handleReadOnlyFunction(params, provider);
11289
+ }
11290
+ // For state-changing functions, ensure we're on the correct chain
11291
+ await this.ensureChain(targetChain);
11292
+ // Resolve the full operation context (address resolution happens here)
11293
+ const resolvedContext = await resolveOperationContext(this, ctx);
11294
+ if (!resolvedContext) {
11295
+ throw new Error('OperationContext resolution failed. Ensure the adapter has capabilities configured.');
11153
11296
  }
11154
- // For state-changing functions, use the original logic
11297
+ const signer = this.getSigner();
11155
11298
  const contract = this.createContractInstance(params, signer, resolvedContext.address, provider);
11156
11299
  return {
11157
11300
  type: 'evm',
@@ -11216,8 +11359,6 @@ class EthersAdapter extends EvmAdapter {
11216
11359
  if (!chain) {
11217
11360
  throw new Error('Chain parameter is required for address resolution. This should be provided by the OperationContext pattern.');
11218
11361
  }
11219
- // Ensure we're on the correct chain before getting address
11220
- await this.ensureChain(chain);
11221
11362
  const signer = this.getSigner();
11222
11363
  const address = await signer.getAddress();
11223
11364
  return address;
@@ -11269,6 +11410,44 @@ class EthersAdapter extends EvmAdapter {
11269
11410
  }
11270
11411
  return feeData.gasPrice;
11271
11412
  }
11413
+ /**
11414
+ * Reads the native token balance (ETH, MATIC, etc.) for a given address.
11415
+ *
11416
+ * @param address - The wallet address to check the balance for.
11417
+ * @param chain - The chain definition to fetch the balance from.
11418
+ * @returns Promise resolving to the balance in wei as a bigint.
11419
+ * @throws Error when balance retrieval fails.
11420
+ *
11421
+ * @example
11422
+ * ```typescript
11423
+ * const balance = await adapter.readNativeBalance(
11424
+ * '0x1234...',
11425
+ * Ethereum
11426
+ * )
11427
+ * console.log('Balance:', balance.toString(), 'wei')
11428
+ * ```
11429
+ */
11430
+ async readNativeBalance(address, chain) {
11431
+ const provider = await this.getProvider(chain);
11432
+ try {
11433
+ return await provider.getBalance(address);
11434
+ }
11435
+ catch (error) {
11436
+ const errorMessage = error instanceof Error ? error.message : String(error);
11437
+ throw new KitError({
11438
+ ...RpcError.ENDPOINT_ERROR,
11439
+ recoverability: 'RETRYABLE',
11440
+ message: `Failed to get native balance for ${address}: ${errorMessage}`,
11441
+ cause: {
11442
+ trace: {
11443
+ operation: 'readNativeBalance',
11444
+ address,
11445
+ chain: chain.name,
11446
+ },
11447
+ },
11448
+ });
11449
+ }
11450
+ }
11272
11451
  /**
11273
11452
  * Signs EIP-712 typed data using the configured signer with OperationContext.
11274
11453
  *
@@ -11383,8 +11562,6 @@ class EthersAdapter extends EvmAdapter {
11383
11562
  if (targetChain.type !== 'evm') {
11384
11563
  throw new Error(`Invalid chain type '${String(targetChain.type)}' for EthersAdapter. Expected 'evm' chain type.`);
11385
11564
  }
11386
- // Ensure adapter is connected to correct chain
11387
- await this.ensureChain(targetChain);
11388
11565
  const signer = this.getSigner();
11389
11566
  if (!signer) {
11390
11567
  throw new Error('No signer is configured. Please provide a signer to sign typed data.');
@@ -11405,13 +11582,11 @@ class EthersAdapter extends EvmAdapter {
11405
11582
  * Handle read-only function execution with noop estimation.
11406
11583
  *
11407
11584
  * @param params - The EVM prepared chain request parameters.
11408
- * @param signer - The Ethers signer instance.
11409
- * @param provider - The Ethers provider instance.
11410
- * @param walletAddress - The wallet address (prevents race conditions in concurrent requests).
11585
+ * @param provider - The Ethers provider instance configured for the target chain.
11411
11586
  * @returns A prepared chain request for read-only function execution.
11412
11587
  */
11413
- async handleReadOnlyFunction(params, signer, provider, walletAddress) {
11414
- const { functionName, args } = params;
11588
+ async handleReadOnlyFunction(params, provider) {
11589
+ const { address, abi: abiInput, functionName, args } = params;
11415
11590
  // For read-only functions, use contract call and noop estimation
11416
11591
  const noopRequest = await createNoopChainRequest();
11417
11592
  return {
@@ -11419,8 +11594,12 @@ class EthersAdapter extends EvmAdapter {
11419
11594
  estimate: noopRequest.estimate,
11420
11595
  execute: async () => {
11421
11596
  try {
11422
- // Create contract instance for read-only function
11423
- const contract = this.createContractInstance(params, signer, walletAddress, provider);
11597
+ // For read-only functions, create contract with provider only (no signer needed)
11598
+ // This ensures we query the correct target chain, not the wallet's current chain
11599
+ const abi = Array.isArray(abiInput) && typeof abiInput[0] === 'string'
11600
+ ? abiInput
11601
+ : abiInput;
11602
+ const contract = new ethers.Contract(address, abi, provider);
11424
11603
  const contractFunction = contract.getFunction(functionName);
11425
11604
  const result = await contractFunction.staticCall(...args);
11426
11605
  // For read-only functions, return the result as a string