Repository: ar-io/ar-io-sdk
Pull request #458: PE-7751 config changes
Author: login: atticusofsparta
Labels: name: released on @alpha
name: released
name: released on @beta
Created: 2025-04-15T15:45:25Z | Merged: 2025-04-16T12:36:40Z

Modifies the turbo arns class to have authenticated and unauthenticated versions, updates signer to be more restricted types based on the message signing we have implmented.

Would like to add some stronger typing on the ARIO writable class to indicate on apis with `fundFrom: "turbo"` whether or not they can be used with turbo base on the class configuration.


Diff:
diff --git a/src/common/io.ts b/src/common/io.ts
index 4fe60d83..8ef3ad92 100644
--- a/src/common/io.ts
+++ b/src/common/io.ts
@@ -13,7 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Signer } from '@dha-team/arbundles';
 import { connect } from '@permaweb/aoconnect';
 import Arweave from 'arweave';
 
@@ -99,7 +98,12 @@ import { defaultArweave } from './arweave.js';
 import { AOProcess } from './contracts/ao-process.js';
 import { InvalidContractConfigurationError } from './error.js';
 import { createFaucet } from './faucet.js';
-import { TurboArNSPaymentProvider } from './turbo.js';
+import {
+  TurboArNSPaymentFactory,
+  TurboArNSPaymentProviderAuthenticated,
+  TurboArNSPaymentProviderUnauthenticated,
+  isTurboArNSSigner,
+} from './turbo.js';
 
 type ARIOConfigNoSigner = OptionalPaymentUrl<
   OptionalArweave<ProcessConfiguration>
@@ -107,6 +111,7 @@ type ARIOConfigNoSigner = OptionalPaymentUrl<
 type ARIOConfigWithSigner = WithSigner<
   OptionalPaymentUrl<OptionalArweave<ProcessConfiguration>>
 >;
+
 type ARIOConfig = ARIOConfigNoSigner | ARIOConfigWithSigner;
 
 export class ARIO {
@@ -203,7 +208,7 @@ export class ARIOReadable implements AoARIORead {
   public readonly process: AOProcess;
   protected epochSettings: AoEpochSettings | undefined;
   protected arweave: Arweave;
-  protected paymentProvider: TurboArNSPaymentProvider; // TODO: this could be an array/map of payment providers
+  protected paymentProvider: TurboArNSPaymentProviderUnauthenticated; // TODO: this could be an array/map of payment providers
 
   constructor(config?: ARIOConfigNoSigner) {
     this.arweave = config?.arweave ?? defaultArweave;
@@ -220,7 +225,7 @@ export class ARIOReadable implements AoARIORead {
     } else {
       throw new InvalidContractConfigurationError();
     }
-    this.paymentProvider = new TurboArNSPaymentProvider({
+    this.paymentProvider = TurboArNSPaymentFactory.init({
       paymentUrl: config?.paymentUrl,
     });
   }
@@ -937,7 +942,9 @@ export class ARIOReadable implements AoARIORead {
 export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
   public declare readonly process: AOProcess;
   private signer: AoSigner;
-  protected paymentProvider: TurboArNSPaymentProvider;
+  protected paymentProvider:
+    | TurboArNSPaymentProviderAuthenticated
+    | TurboArNSPaymentProviderUnauthenticated;
 
   constructor({ signer, paymentUrl, ...config }: ARIOConfigWithSigner) {
     if (config === undefined) {
@@ -950,8 +957,8 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
       super(config);
     }
     this.signer = createAoSigner(signer);
-    this.paymentProvider = new TurboArNSPaymentProvider({
-      signer: signer as Signer,
+    this.paymentProvider = TurboArNSPaymentFactory.init({
+      signer: isTurboArNSSigner(signer) ? signer : undefined,
       paymentUrl,
     });
   }
@@ -1352,6 +1359,13 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
     if (params.fundFrom === 'turbo') {
+      if (
+        !(this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated)
+      ) {
+        throw new Error(
+          'Turbo funding is not supported for this payment provider',
+        );
+      }
       return this.paymentProvider.initiateArNSPurchase({
         intent: 'Buy-Name',
         ...params,
@@ -1388,6 +1402,13 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
     if (params.fundFrom === 'turbo') {
+      if (
+        !(this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated)
+      ) {
+        throw new Error(
+          'Turbo funding is not supported for this payment provider',
+        );
+      }
       return this.paymentProvider.initiateArNSPurchase({
         intent: 'Upgrade-Name',
         name: params.name,
@@ -1421,6 +1442,13 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
     if (params.fundFrom === 'turbo') {
+      if (
+        !(this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated)
+      ) {
+        throw new Error(
+          'Turbo funding is not supported for this payment provider',
+        );
+      }
       return this.paymentProvider.initiateArNSPurchase({
         intent: 'Extend-Lease',
         ...params,
@@ -1446,6 +1474,13 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
     if (params.fundFrom === 'turbo') {
+      if (
+        !(this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated)
+      ) {
+        throw new Error(
+          'Turbo funding is not supported for this payment provider',
+        );
+      }
       return this.paymentProvider.initiateArNSPurchase({
         intent: 'Increase-Undername-Limit',
         ...params,
diff --git a/src/common/turbo.ts b/src/common/turbo.ts
index 120c91fd..847d958c 100644
--- a/src/common/turbo.ts
+++ b/src/common/turbo.ts
@@ -13,7 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { ArconnectSigner, SignatureConfig } from '@dha-team/arbundles';
+import {
+  ArconnectSigner,
+  ArweaveSigner,
+  EthereumSigner,
+  InjectedEthereumSigner,
+  SignatureConfig,
+  Signer,
+} from '@dha-team/arbundles';
 import { AxiosInstance, RawAxiosRequestHeaders } from 'axios';
 import { v4 as uuidv4 } from 'uuid';
 
@@ -21,7 +28,6 @@ import {
   AoMessageResult,
   TransactionId,
   TurboArNSSigner,
-  WriteOptions,
 } from '../types/common.js';
 import { AoTokenCostParams } from '../types/io.js';
 import { mARIOToken } from '../types/token.js';
@@ -30,14 +36,19 @@ import { createAxiosInstance } from '../utils/http-client.js';
 import { urlWithSearchParams } from '../utils/url.js';
 import { ILogger, Logger } from './logger.js';
 
-export interface TurboConfig {
+// Define separate config interfaces
+export interface TurboUnauthenticatedConfig {
   // The URL of the Turbo payment service
   paymentUrl?: string;
   // The logger to use
   logger?: ILogger;
   // The HTTP client to use
   axios?: AxiosInstance;
-  signer?: TurboArNSSigner;
+}
+
+export interface TurboAuthenticatedConfig extends TurboUnauthenticatedConfig {
+  // The signer required for authenticated operations
+  signer: TurboArNSSigner;
 }
 
 export async function signedRequestHeadersFromSigner({
@@ -47,31 +58,72 @@ export async function signedRequestHeadersFromSigner({
   signer: TurboArNSSigner;
   nonce?: string;
 }): Promise<RawAxiosRequestHeaders> {
-  await (signer as ArconnectSigner).setPublicKey?.();
-  const signature = await signer.sign(Uint8Array.from(Buffer.from(nonce)));
+  let signature: string | undefined = undefined;
+  let publicKey: string | undefined = undefined;
+
+  const signatureType = isWanderArweaveBrowserSigner(signer)
+    ? SignatureConfig.ARWEAVE
+    : (signer as Signer).signatureType;
 
-  let publicKey: string;
-  switch (signer.signatureType) {
+  // equivalent to window.arweaveWallet
+  if (isWanderArweaveBrowserSigner(signer)) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer.signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
+  } else if (signer instanceof ArconnectSigner) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer['signer'].signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
+  } else if (
+    signer instanceof ArweaveSigner ||
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner
+  ) {
+    if ('setPublicKey' in signer) {
+      await signer.setPublicKey();
+    }
+    signature = toB64Url(
+      Buffer.from(await signer.sign(Uint8Array.from(Buffer.from(nonce)))),
+    );
+  }
+
+  switch (signatureType) {
     case SignatureConfig.ARWEAVE:
-      publicKey = toB64Url(signer.publicKey);
+      if (isWanderArweaveBrowserSigner(signer)) {
+        publicKey = await signer.getActivePublicKey();
+      } else if ('setPublicKey' in signer) {
+        await signer.setPublicKey();
+        publicKey = toB64Url(signer.publicKey);
+      }
       break;
     case SignatureConfig.ETHEREUM:
-      publicKey = '0x' + signer.publicKey.toString('hex');
+      if ('publicKey' in signer) {
+        publicKey = '0x' + signer.publicKey.toString('hex');
+      } else {
+        throw new Error('Public key not found');
+      }
       break;
     // TODO: solana sig support
     // case SignatureConfig.SOLANA:
     // case SignatureConfig.ED25519:
     default:
       throw new Error(
-        `Unsupported signer type for signing requests: ${signer.signatureType}`,
+        `Unsupported signer type for signing requests: ${signatureType}`,
       );
   }
 
+  if (publicKey === undefined || signature === undefined) {
+    throw new Error('Public key or signature not found');
+  }
   return {
     'x-public-key': publicKey,
     'x-nonce': nonce,
-    'x-signature': toB64Url(Buffer.from(signature)),
-    'x-signature-type': signer.signatureType,
+    'x-signature': signature,
+    'x-signature-type': signatureType,
   };
 }
 
@@ -82,6 +134,7 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
+// Define separate provider interfaces
 export interface ArNSPaymentProvider {
   // TODO: have this return just the number, for generic payment providers
   /** Returns the cost of the action in the Payment Provider's native currency (winc for Turbo) */
@@ -90,28 +143,71 @@ export interface ArNSPaymentProvider {
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider extends ArNSPaymentProvider {
   initiateArNSPurchase(
     params: AoTokenCostParams & { processId?: TransactionId },
-    options: WriteOptions,
   ): Promise<AoMessageResult<ArNSPurchaseReceipt>>;
 }
 
-export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
-  private readonly paymentUrl: string;
-  private readonly axios: AxiosInstance;
-  private readonly logger: ILogger;
-  private readonly signer?: TurboArNSSigner;
+export class TurboArNSPaymentFactory {
+  static init(): TurboArNSPaymentProviderUnauthenticated;
+  // Overload: without signer, will return unauthenticated provider
+  static init({
+    paymentUrl,
+    axios,
+    logger,
+  }: TurboUnauthenticatedConfig & {
+    signer?: TurboArNSSigner;
+  }): TurboArNSPaymentProviderUnauthenticated;
+  // Overload: with signer, will return authenticated provider
+  static init({
+    signer,
+    paymentUrl,
+    axios,
+    logger,
+  }: TurboAuthenticatedConfig): TurboArNSPaymentProviderAuthenticated;
+  static init(
+    config?:
+      | TurboAuthenticatedConfig
+      | (TurboUnauthenticatedConfig & { signer?: TurboArNSSigner }),
+  ):
+    | TurboArNSPaymentProviderAuthenticated
+    | TurboArNSPaymentProviderUnauthenticated {
+    const { signer, paymentUrl, axios, logger } = config ?? {};
+    if (signer !== undefined) {
+      return new TurboArNSPaymentProviderAuthenticated({
+        signer,
+        paymentUrl,
+        axios,
+        logger,
+      });
+    }
+    return new TurboArNSPaymentProviderUnauthenticated({
+      paymentUrl,
+      axios,
+      logger,
+    });
+  }
+}
+
+// Base class for unauthenticated operations
+export class TurboArNSPaymentProviderUnauthenticated
+  implements ArNSPaymentProvider
+{
+  protected readonly paymentUrl: string;
+  protected readonly axios: AxiosInstance;
+  protected readonly logger: ILogger;
 
   constructor({
     paymentUrl = 'https://payment.ardrive.io',
     axios = createAxiosInstance(),
     logger = Logger.default,
-    signer,
-  }: TurboConfig) {
+  }: TurboUnauthenticatedConfig) {
     this.paymentUrl = paymentUrl;
     this.axios = axios;
     this.logger = logger;
-    this.signer = signer;
   }
 
   public async getArNSPriceDetails({
@@ -164,6 +260,22 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     const { winc } = await this.getArNSPriceDetails(params);
     return +winc;
   }
+}
+
+// Class for authenticated operations, extending the base class
+export class TurboArNSPaymentProviderAuthenticated
+  extends TurboArNSPaymentProviderUnauthenticated
+  implements ArNSAuthenticatedPaymentProvider
+{
+  private readonly signer: TurboArNSSigner;
+
+  constructor({ signer, ...restConfig }: TurboAuthenticatedConfig) {
+    super(restConfig); // Pass unauthenticated config to base class+
+    if (!isTurboArNSSigner(signer)) {
+      throw new Error('Signer must be a TurboArNSSigner');
+    }
+    this.signer = signer;
+  }
 
   public async initiateArNSPurchase({
     intent,
@@ -174,13 +286,9 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     years,
   }: AoTokenCostParams & {
     processId?: TransactionId;
+    // options parameter removed as it wasn't used
   }): Promise<AoMessageResult<ArNSPurchaseReceipt>> {
-    if (!this.signer) {
-      throw new Error(
-        'Signer required for initiating ArNS purchase with Turbo',
-      );
-    }
-
+    // Signer check is implicitly handled by requiring it in the constructor
     const url = urlWithSearchParams({
       baseUrl: `${this.paymentUrl}/v1/arns/purchase/${intent}/${name}`,
       params: {
@@ -225,3 +333,27 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     };
   }
 }
+
+type WanderWallet = {
+  signMessage: (message: Uint8Array) => Promise<Uint8Array>;
+  getActivePublicKey: () => Promise<string>;
+};
+
+function isWanderArweaveBrowserSigner(signer: unknown): signer is WanderWallet {
+  return (
+    typeof signer === 'object' &&
+    signer !== null &&
+    'signMessage' in signer &&
+    'getActivePublicKey' in signer
+  );
+}
+
+export function isTurboArNSSigner(signer: unknown): signer is TurboArNSSigner {
+  const isWanderWallet = isWanderArweaveBrowserSigner(signer);
+  const isSigner =
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner ||
+    signer instanceof ArweaveSigner ||
+    signer instanceof ArconnectSigner;
+  return isWanderWallet || isSigner;
+}
diff --git a/src/types/common.ts b/src/types/common.ts
index af4ff05a..24cbd5ea 100644
--- a/src/types/common.ts
+++ b/src/types/common.ts
@@ -13,7 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Signer } from '@dha-team/arbundles';
+import {
+  ArconnectSigner,
+  ArweaveSigner,
+  EthereumSigner,
+  InjectedEthereumSigner,
+  Signer,
+} from '@dha-team/arbundles';
 import {
   dryrun,
   message,
@@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
 export type ContractSigner = Signer | Window['arweaveWallet'] | AoSigner;
 export type WithSigner<T = NonNullable<unknown>> = {
   signer: ContractSigner;
diff --git a/src/web/index.ts b/src/web/index.ts
index cd943831..36cd1f26 100644
--- a/src/web/index.ts
+++ b/src/web/index.ts
@@ -13,7 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-export { ArweaveSigner, ArconnectSigner } from '@dha-team/arbundles';
+export {
+  ArweaveSigner,
+  ArconnectSigner,
+  InjectedEthereumSigner,
+  EthereumSigner,
+} from '@dha-team/arbundles';
 
 export * from '../types/index.js';
 export * from '../common/index.js';


Reviews:
user: login: fedellen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:30:09Z
user: login: fedellen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:31:00Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:39:22Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:42:20Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:42:36Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:43:05Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:44:06Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:45:29Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:46:56Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:47:18Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:47:48Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:51:18Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:51:49Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:04:22Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:46:37Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:49:11Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:56:55Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:00:41Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:25:21Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:30:55Z
user: login: fedellen
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:31:32Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:39:24Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:41:31Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:42:39Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:53:03Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:53:28Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:53:48Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:54:23Z
user: login: dtfiedler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:55:16Z
user: login: atticusofsparta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T22:02:54Z
user: login: fedellen
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T12:25:02Z

Review comments:
diff_hunk: @@ -225,3 +279,26 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     };
   }
 }
+
+type WanderWallet = {
+  signMessage: (message: Uint8Array) => Promise<Uint8Array>;
+  getActivePublicKey: () => Promise<string>;
+};
+
+function isWanderArweaveBrowserSigner(signer: unknown): signer is WanderWallet {
+  return (
+    typeof signer === 'object' &&
+    signer !== null &&
+    'signMessage' in signer &&
+    'getActivePublicKey' in signer
+  );
+}
+
+export function isTurboArNSSigner(signer: unknown): signer is TurboArNSSigner {
+  const isWanderWallet = isWanderArweaveBrowserSigner(signer);
+  const isSigner =
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner ||
+    signer instanceof ArweaveSigner;
path: src/common/turbo.ts
user: login: fedellen
body: add ArconnectSigner
created_at: 2025-04-15T17:30:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,31 +58,57 @@ export async function signedRequestHeadersFromSigner({
   signer: TurboArNSSigner;
   nonce?: string;
 }): Promise<RawAxiosRequestHeaders> {
-  await (signer as ArconnectSigner).setPublicKey?.();
-  const signature = await signer.sign(Uint8Array.from(Buffer.from(nonce)));
-
-  let publicKey: string;
-  switch (signer.signatureType) {
-    case SignatureConfig.ARWEAVE:
-      publicKey = toB64Url(signer.publicKey);
-      break;
-    case SignatureConfig.ETHEREUM:
-      publicKey = '0x' + signer.publicKey.toString('hex');
-      break;
-    // TODO: solana sig support
-    // case SignatureConfig.SOLANA:
-    // case SignatureConfig.ED25519:
-    default:
-      throw new Error(
-        `Unsupported signer type for signing requests: ${signer.signatureType}`,
-      );
-  }
+  let signature: string | undefined = undefined;
+  let publicKey: string | undefined = undefined;
+
path: src/common/turbo.ts
user: login: fedellen
body: ```suggestion
if (signer instanceof ArconnectSigner) signer = signer['signer'];
```
created_at: 2025-04-15T17:31:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -950,10 +956,14 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
       super(config);
     }
     this.signer = createAoSigner(signer);
-    this.paymentProvider = new TurboArNSPaymentProvider({
-      signer: signer as Signer,
-      paymentUrl,
-    });
+    this.paymentProvider = isTurboArNSSigner(signer)
+      ? new TurboArNSPaymentProviderAuthenticated({
+          signer,
+          paymentUrl,
+        })
+      : new TurboArNSPaymentProviderUnauthenticated({
+          paymentUrl,
+        });
path: src/common/io.ts
user: login: dtfiedler
body: i'd prefer to just have a factory utility or class that returns the signer vs. the ternary here.

i.e. `TurboPaymentProvider.init({ paymentUrl, signer })`
created_at: 2025-04-15T17:39:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,31 +58,57 @@ export async function signedRequestHeadersFromSigner({
   signer: TurboArNSSigner;
   nonce?: string;
 }): Promise<RawAxiosRequestHeaders> {
-  await (signer as ArconnectSigner).setPublicKey?.();
-  const signature = await signer.sign(Uint8Array.from(Buffer.from(nonce)));
-
-  let publicKey: string;
-  switch (signer.signatureType) {
-    case SignatureConfig.ARWEAVE:
-      publicKey = toB64Url(signer.publicKey);
-      break;
-    case SignatureConfig.ETHEREUM:
-      publicKey = '0x' + signer.publicKey.toString('hex');
-      break;
-    // TODO: solana sig support
-    // case SignatureConfig.SOLANA:
-    // case SignatureConfig.ED25519:
-    default:
-      throw new Error(
-        `Unsupported signer type for signing requests: ${signer.signatureType}`,
-      );
-  }
+  let signature: string | undefined = undefined;
+  let publicKey: string | undefined = undefined;
+
path: src/common/turbo.ts
user: login: atticusofsparta
body: Not a fan of overriding arguments, but this accomplishes the same thing: [35ae6aa](https://github.com/ar-io/ar-io-sdk/pull/458/commits/35ae6aa2f2e7a5429c35df9a4a4cabbbb499fed3)
created_at: 2025-04-15T17:42:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,3 +279,26 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     };
   }
 }
+
+type WanderWallet = {
+  signMessage: (message: Uint8Array) => Promise<Uint8Array>;
+  getActivePublicKey: () => Promise<string>;
+};
+
+function isWanderArweaveBrowserSigner(signer: unknown): signer is WanderWallet {
+  return (
+    typeof signer === 'object' &&
+    signer !== null &&
+    'signMessage' in signer &&
+    'getActivePublicKey' in signer
+  );
+}
+
+export function isTurboArNSSigner(signer: unknown): signer is TurboArNSSigner {
+  const isWanderWallet = isWanderArweaveBrowserSigner(signer);
+  const isSigner =
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner ||
+    signer instanceof ArweaveSigner;
path: src/common/turbo.ts
user: login: atticusofsparta
body: [35ae6aa](https://github.com/ar-io/ar-io-sdk/pull/458/commits/35ae6aa2f2e7a5429c35df9a4a4cabbbb499fed3)
created_at: 2025-04-15T17:42:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1445,7 +1464,10 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     params: AoIncreaseUndernameLimitParams,
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
-    if (params.fundFrom === 'turbo') {
+    if (
+      this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated &&
path: src/common/io.ts
user: login: dtfiedler
body: replace paymentProviders to be an Record<'turbo', PaymentProvider> and then modify this logic to do `this.paymentProvider[fundFrom].initiatiteArNSPurchase()`
created_at: 2025-04-15T17:43:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -203,7 +207,7 @@ export class ARIOReadable implements AoARIORead {
   public readonly process: AOProcess;
   protected epochSettings: AoEpochSettings | undefined;
   protected arweave: Arweave;
-  protected paymentProvider: TurboArNSPaymentProvider; // TODO: this could be an array/map of payment providers
+  protected paymentProvider: TurboArNSPaymentProviderUnauthenticated; // TODO: this could be an array/map of payment providers
path: src/common/io.ts
user: login: dtfiedler
body: see other comment - modify this to be `Record<'turbo', ArNSPaymentProvider >`; and access it via `fundFrom`
created_at: 2025-04-15T17:44:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -225,3 +279,26 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     };
   }
 }
+
+type WanderWallet = {
+  signMessage: (message: Uint8Array) => Promise<Uint8Array>;
+  getActivePublicKey: () => Promise<string>;
+};
+
+function isWanderArweaveBrowserSigner(signer: unknown): signer is WanderWallet {
+  return (
+    typeof signer === 'object' &&
+    signer !== null &&
+    'signMessage' in signer &&
+    'getActivePublicKey' in signer
+  );
+}
+
+export function isTurboArNSSigner(signer: unknown): signer is TurboArNSSigner {
+  const isWanderWallet = isWanderArweaveBrowserSigner(signer);
+  const isSigner =
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner ||
+    signer instanceof ArweaveSigner;
path: src/common/turbo.ts
user: login: dtfiedler
body: does it need to be an instance of one of these, or just have a `sign()` function?
created_at: 2025-04-15T17:45:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: dtfiedler
body: I'm considered we are not sufficiently abstracting signers. Ideally any `Signer` used satisfies the necesssary interface we determine based on the integration points with AO and Turbo. It already seems confusing to me you could have a `TurboArNSSigner` which is not satisfied by an `AoSigner` or vice versa
created_at: 2025-04-15T17:46:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: dtfiedler
body: how do we get unit tests for each signer type? what kind of lift that would be?
created_at: 2025-04-15T17:47:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,31 +59,66 @@ export async function signedRequestHeadersFromSigner({
   signer: TurboArNSSigner;
   nonce?: string;
 }): Promise<RawAxiosRequestHeaders> {
-  await (signer as ArconnectSigner).setPublicKey?.();
-  const signature = await signer.sign(Uint8Array.from(Buffer.from(nonce)));
-
-  let publicKey: string;
-  switch (signer.signatureType) {
-    case SignatureConfig.ARWEAVE:
-      publicKey = toB64Url(signer.publicKey);
-      break;
-    case SignatureConfig.ETHEREUM:
-      publicKey = '0x' + signer.publicKey.toString('hex');
-      break;
-    // TODO: solana sig support
-    // case SignatureConfig.SOLANA:
-    // case SignatureConfig.ED25519:
-    default:
-      throw new Error(
-        `Unsupported signer type for signing requests: ${signer.signatureType}`,
-      );
+  let signature: string | undefined = undefined;
+  let publicKey: string | undefined = undefined;
+
+  const signatureType = isWanderArweaveBrowserSigner(signer)
+    ? SignatureConfig.ARWEAVE
+    : (signer as Signer).signatureType;
+
+  if (signer instanceof ArconnectSigner) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer['signer'].signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
   }
 
+  // equivalent to window.arweaveWallet
+  if (isWanderArweaveBrowserSigner(signer)) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer.signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
+  } else if (
+    signer instanceof ArweaveSigner ||
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner
+  ) {
path: src/common/turbo.ts
user: login: dtfiedler
body: isn't this the as `isTurboArNSSigner`?
created_at: 2025-04-15T17:47:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,36 +129,41 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
-export interface ArNSPaymentProvider {
+// Define separate provider interfaces
+export interface ArNSUnauthenticatedPaymentProvider {
path: src/common/turbo.ts
user: login: dtfiedler
body: ```suggestion
export interface ArNSPaymentProvider {
```
created_at: 2025-04-15T17:51:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,36 +129,41 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
-export interface ArNSPaymentProvider {
+// Define separate provider interfaces
+export interface ArNSUnauthenticatedPaymentProvider {
   // TODO: have this return just the number, for generic payment providers
   /** Returns the cost of the action in the Payment Provider's native currency (winc for Turbo) */
   getPrice(params: AoTokenCostParams): Promise<number>;
   getArNSPriceDetails(params: AoTokenCostParams): Promise<{
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider
+  extends ArNSUnauthenticatedPaymentProvider {
   initiateArNSPurchase(
     params: AoTokenCostParams & { processId?: TransactionId },
-    options: WriteOptions,
+    options: WriteOptions, // Note: WriteOptions might not be necessary here if unused
path: src/common/turbo.ts
user: login: dtfiedler
body: remove it
created_at: 2025-04-15T17:51:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -47,31 +59,66 @@ export async function signedRequestHeadersFromSigner({
   signer: TurboArNSSigner;
   nonce?: string;
 }): Promise<RawAxiosRequestHeaders> {
-  await (signer as ArconnectSigner).setPublicKey?.();
-  const signature = await signer.sign(Uint8Array.from(Buffer.from(nonce)));
-
-  let publicKey: string;
-  switch (signer.signatureType) {
-    case SignatureConfig.ARWEAVE:
-      publicKey = toB64Url(signer.publicKey);
-      break;
-    case SignatureConfig.ETHEREUM:
-      publicKey = '0x' + signer.publicKey.toString('hex');
-      break;
-    // TODO: solana sig support
-    // case SignatureConfig.SOLANA:
-    // case SignatureConfig.ED25519:
-    default:
-      throw new Error(
-        `Unsupported signer type for signing requests: ${signer.signatureType}`,
-      );
+  let signature: string | undefined = undefined;
+  let publicKey: string | undefined = undefined;
+
+  const signatureType = isWanderArweaveBrowserSigner(signer)
+    ? SignatureConfig.ARWEAVE
+    : (signer as Signer).signatureType;
+
+  if (signer instanceof ArconnectSigner) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer['signer'].signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
   }
 
+  // equivalent to window.arweaveWallet
+  if (isWanderArweaveBrowserSigner(signer)) {
+    signature = toB64Url(
+      Buffer.from(
+        await signer.signMessage(Uint8Array.from(Buffer.from(nonce))),
+      ),
+    );
+  } else if (
+    signer instanceof ArweaveSigner ||
+    signer instanceof EthereumSigner ||
+    signer instanceof InjectedEthereumSigner
+  ) {
path: src/common/turbo.ts
user: login: atticusofsparta
body: No - isTurboArNSSigner includes browser signer which doesnt implement the Signer interface from arbundles.
created_at: 2025-04-15T20:04:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -950,10 +956,14 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
       super(config);
     }
     this.signer = createAoSigner(signer);
-    this.paymentProvider = new TurboArNSPaymentProvider({
-      signer: signer as Signer,
-      paymentUrl,
-    });
+    this.paymentProvider = isTurboArNSSigner(signer)
+      ? new TurboArNSPaymentProviderAuthenticated({
+          signer,
+          paymentUrl,
+        })
+      : new TurboArNSPaymentProviderUnauthenticated({
+          paymentUrl,
+        });
path: src/common/io.ts
user: login: atticusofsparta
body: [0527b83](https://github.com/ar-io/ar-io-sdk/pull/458/commits/0527b8357fe47f1dee8831832b43a520555a5770)
created_at: 2025-04-15T20:46:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1445,7 +1464,10 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     params: AoIncreaseUndernameLimitParams,
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
-    if (params.fundFrom === 'turbo') {
+    if (
+      this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated &&
path: src/common/io.ts
user: login: atticusofsparta
body: so this would translate to `this.paymentProviders['stakes' | 'balances' | 'any' | 'turbo'] ?
created_at: 2025-04-15T20:49:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1445,7 +1464,10 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     params: AoIncreaseUndernameLimitParams,
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
-    if (params.fundFrom === 'turbo') {
+    if (
+      this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated &&
path: src/common/io.ts
user: login: dtfiedler
body: you'd keep the `(if params.fundFrom == 'turbo')` but remove `this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated`
created_at: 2025-04-15T20:56:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: atticusofsparta
body: I agree - the issue we are facing here is we made the decision to not use data items for auth and instead sign messages - the tradeoff there is we need to access specific methods, like signMessage on arweaveWallet api, to be able to execute that. It also means we need to implement the api on each specific signer type. Might be able to be a bit looser here and use ContractSigner to a degree - can't use AoSigner because that one is _specifically_ for signing data items, but I think we can abstract it to Signer and Window['arweaveWallet'] atleast.
created_at: 2025-04-15T21:00:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,36 +129,41 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
-export interface ArNSPaymentProvider {
+// Define separate provider interfaces
+export interface ArNSUnauthenticatedPaymentProvider {
path: src/common/turbo.ts
user: login: atticusofsparta
body: So we would have ArNSPaymentProvider and ArNSAuthenticatedPaymentProvider?
created_at: 2025-04-15T21:25:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: atticusofsparta
body: I think I _can_ use Signer but its a bit wierd to allow all SIgners when in the headers signing we throw if its not an arweave or eth signature type
created_at: 2025-04-15T21:30:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: atticusofsparta
body: node signers are easy to unit test - the web ones a little less so. Injected provider can probably be unit tested with an ethers non web provider, but arconnect gets tricky, needs a mock.
created_at: 2025-04-15T21:39:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,36 +129,41 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
-export interface ArNSPaymentProvider {
+// Define separate provider interfaces
+export interface ArNSUnauthenticatedPaymentProvider {
path: src/common/turbo.ts
user: login: atticusofsparta
body: https://github.com/ar-io/ar-io-sdk/pull/458/commits/336d52f03cd5ed7cb49173e26f4e3838a9b7cf91
created_at: 2025-04-15T21:41:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,36 +129,41 @@ export type ArNSPurchaseReceipt = AoTokenCostParams & {
   createdDate: string;
 };
 
-export interface ArNSPaymentProvider {
+// Define separate provider interfaces
+export interface ArNSUnauthenticatedPaymentProvider {
   // TODO: have this return just the number, for generic payment providers
   /** Returns the cost of the action in the Payment Provider's native currency (winc for Turbo) */
   getPrice(params: AoTokenCostParams): Promise<number>;
   getArNSPriceDetails(params: AoTokenCostParams): Promise<{
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider
+  extends ArNSUnauthenticatedPaymentProvider {
   initiateArNSPurchase(
     params: AoTokenCostParams & { processId?: TransactionId },
-    options: WriteOptions,
+    options: WriteOptions, // Note: WriteOptions might not be necessary here if unused
path: src/common/turbo.ts
user: login: atticusofsparta
body: [336d52f](https://github.com/ar-io/ar-io-sdk/pull/458/commits/336d52f03cd5ed7cb49173e26f4e3838a9b7cf91)
created_at: 2025-04-15T21:42:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,28 +138,72 @@ export interface ArNSPaymentProvider {
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider extends ArNSPaymentProvider {
path: src/common/turbo.ts
user: login: dtfiedler
body: ```suggestion
export interface ArNSSignedPaymentProvider extends ArNSPaymentProvider {
```
created_at: 2025-04-15T21:53:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,28 +138,72 @@ export interface ArNSPaymentProvider {
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider extends ArNSPaymentProvider {
   initiateArNSPurchase(
     params: AoTokenCostParams & { processId?: TransactionId },
-    options: WriteOptions,
+    options: WriteOptions, // Note: WriteOptions might not be necessary here if unused
   ): Promise<AoMessageResult<ArNSPurchaseReceipt>>;
 }
 
-export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
-  private readonly paymentUrl: string;
-  private readonly axios: AxiosInstance;
-  private readonly logger: ILogger;
-  private readonly signer?: TurboArNSSigner;
+export class TurboArNSPaymentFactory {
path: src/common/turbo.ts
user: login: dtfiedler
body: TurboPaymentProvider
```suggestion
export class TurboPaymentProvider {
```
created_at: 2025-04-15T21:53:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,28 +138,72 @@ export interface ArNSPaymentProvider {
     winc: string;
     mARIO: mARIOToken;
   }>;
+}
+
+export interface ArNSAuthenticatedPaymentProvider extends ArNSPaymentProvider {
   initiateArNSPurchase(
     params: AoTokenCostParams & { processId?: TransactionId },
-    options: WriteOptions,
+    options: WriteOptions, // Note: WriteOptions might not be necessary here if unused
   ): Promise<AoMessageResult<ArNSPurchaseReceipt>>;
 }
 
-export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
-  private readonly paymentUrl: string;
-  private readonly axios: AxiosInstance;
-  private readonly logger: ILogger;
-  private readonly signer?: TurboArNSSigner;
+export class TurboArNSPaymentFactory {
+  static init(): TurboArNSPaymentProviderUnauthenticated;
+  // Overload: without signer, will return unauthenticated provider
+  static init({
+    paymentUrl,
+    axios,
+    logger,
+  }: TurboUnauthenticatedConfig & {
+    signer?: TurboArNSSigner;
+  }): TurboArNSPaymentProviderUnauthenticated;
+  // Overload: with signer, will return authenticated provider
+  static init({
+    signer,
+    paymentUrl,
+    axios,
+    logger,
+  }: TurboAuthenticatedConfig): TurboArNSPaymentProviderAuthenticated;
+  static init(
+    config?:
+      | TurboAuthenticatedConfig
+      | (TurboUnauthenticatedConfig & { signer?: TurboArNSSigner }),
+  ):
+    | TurboArNSPaymentProviderAuthenticated
+    | TurboArNSPaymentProviderUnauthenticated {
+    const { signer, paymentUrl, axios, logger } = config ?? {};
+    if (signer !== undefined) {
+      return new TurboArNSPaymentProviderAuthenticated({
+        signer,
+        paymentUrl,
+        axios,
+        logger,
+      });
+    }
+    return new TurboArNSPaymentProviderUnauthenticated({
+      paymentUrl,
+      axios,
+      logger,
+    });
+  }
+}
+
+// Base class for unauthenticated operations
+export class TurboArNSPaymentProviderUnauthenticated
path: src/common/turbo.ts
user: login: dtfiedler
body: ```suggestion
export class TurboArNSPaymentProvider
```
created_at: 2025-04-15T21:53:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -164,6 +256,22 @@ export class TurboArNSPaymentProvider implements ArNSPaymentProvider {
     const { winc } = await this.getArNSPriceDetails(params);
     return +winc;
   }
+}
+
+// Class for authenticated operations, extending the base class
+export class TurboArNSPaymentProviderAuthenticated
path: src/common/turbo.ts
user: login: dtfiedler
body: ```suggestion
export class TurboArNSSignedPaymentProvider
```
created_at: 2025-04-15T21:54:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -39,7 +45,13 @@ export type OptionalArweave<T = NonNullable<unknown>> = {
 export type OptionalPaymentUrl<T = NonNullable<unknown>> = {
   paymentUrl?: string;
 } & T;
-export type TurboArNSSigner = Signer;
+// TODO: TurboArNSSigner could be simply `Signer` but we need to implement each message signing method for signing headers.
+export type TurboArNSSigner =
+  | EthereumSigner
+  | InjectedEthereumSigner
+  | ArweaveSigner
+  | ArconnectSigner
+  | Window['arweaveWallet'];
path: src/types/common.ts
user: login: dtfiedler
body: do you intend on adding tests?
created_at: 2025-04-15T21:55:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1445,7 +1464,10 @@ export class ARIOWriteable extends ARIOReadable implements AoARIOWrite {
     params: AoIncreaseUndernameLimitParams,
     options?: WriteOptions,
   ): Promise<AoMessageResult> {
-    if (params.fundFrom === 'turbo') {
+    if (
+      this.paymentProvider instanceof TurboArNSPaymentProviderAuthenticated &&
path: src/common/io.ts
user: login: atticusofsparta
body: this is a bit of a wierd state because we still need to ensure the payment provider is authenticated - due to the inconsistent signers, it might not be. Is the intention here to allow for easily selecting other payment providers in the future?
created_at: 2025-04-15T22:02:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: dtfiedler
created_at: 2025-04-16T12:47:38Z
body: :tada: This PR is included in version 3.10.0-alpha.3 :tada:

The release is available on:
- [npm package (@alpha dist-tag)](https://www.npmjs.com/package/@ar.io/sdk/v/3.10.0-alpha.3)
- [GitHub release](https://github.com/ar-io/ar-io-sdk/releases/tag/v3.10.0-alpha.3)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR
user: login: dtfiedler
created_at: 2025-04-25T14:49:12Z
body: :tada: This PR is included in version 3.11.0-alpha.1 :tada:

The release is available on:
- [npm package (@alpha dist-tag)](https://www.npmjs.com/package/@ar.io/sdk/v/3.11.0-alpha.1)
- [GitHub release](https://github.com/ar-io/ar-io-sdk/releases/tag/v3.11.0-alpha.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR
user: login: dtfiedler
created_at: 2025-05-20T15:56:29Z
body: :tada: This PR is included in version 3.11.0-beta.1 :tada:

The release is available on:
- [npm package (@beta dist-tag)](https://www.npmjs.com/package/@ar.io/sdk/v/3.11.0-beta.1)
- [GitHub release](https://github.com/ar-io/ar-io-sdk/releases/tag/v3.11.0-beta.1)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-20T15:56:35Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ar-io/ar-io-sdk&utm_content=458):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: dtfiedler
created_at: 2025-05-20T18:46:33Z
body: :tada: This PR is included in version 3.11.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/@ar.io/sdk/v/3.11.0)
- [GitHub release](https://github.com/ar-io/ar-io-sdk/releases/tag/v3.11.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: CONTRIBUTOR