Repository: 0xs34n/starknet.js (ts)

Issue:
getStarkName() should return empty string when no starkname found
**Describe the bug**

Currently `account.getStarkName()` returns "stark" when address has no stark name. 
This is due to `useDecoded` function in starknetId utils which concat "stark" at the end of the result from the call to the the starknet.id naming contract, even if it's empty.

https://github.com/0xs34n/starknet.js/blob/b0f4b7690b471b1c8edbbebaa4f9b64feb124d00/src/utils/starknetId.ts#L22


**To Reproduce**

```
const account = new Account(provider, address, ec.genKeyPair())
const result = await account.getStarkName()
```

**Expected behavior**
`account.getStarkName()` should return an empty string when address has no associated stark name. 

**Desktop (please complete the following information):**

- Node version `19.4.0`
- StarkNet.js version : `4.15.0`
- Network `devnet`


Pull request:
Feature/move-starkname-to-provider
## Motivation and Resolution

Starkname should be available in the provider to use.

## Usage related changes

- Provider has `getStarkName` and `getAddressFromStarkName` now
- Account changes from `getStarkName(contract?: BigNumberish)` to `getStarkName(address?: BigNumberish, contract?: BigNumberish)`
- fixes #519

## Checklist:

- [x] Performed a self-review of the code
- [x] Rebased to the last commit of the target branch (or merged it into my branch)
- [x] Linked the issues which this PR resolves
- [x] Documented the changes
- [x] Updated the docs (www)
- [x] Updated the tests
- [x] All tests are passing


Patch:
diff --git a/src/account/default.ts b/src/account/default.ts
index ac178567..b25c2a2b 100644
--- a/src/account/default.ts
+++ b/src/account/default.ts
@@ -1,5 +1,3 @@
-import { BN } from 'bn.js';
-
 import { UDC, ZERO } from '../constants';
 import { ProviderInterface, ProviderOptions } from '../provider';
 import { Provider } from '../provider/default';
@@ -39,10 +37,9 @@ import {
   pedersen,
   transactionVersion,
 } from '../utils/hash';
-import { BigNumberish, hexToDecimalString, toBN, toCairoBool } from '../utils/number';
+import { BigNumberish, toBN, toCairoBool } from '../utils/number';
 import { parseContract } from '../utils/provider';
 import { compileCalldata, estimatedFeeToMaxFee, randomAddress } from '../utils/stark';
-import { getStarknetIdContract, useDecoded, useEncoded } from '../utils/starknetId';
 import { fromCallsToExecuteCalldata } from '../utils/transaction';
 import { TypedData, getMessageHash } from '../utils/typedData';
 import { AccountInterface } from './interface';
@@ -67,52 +64,6 @@ export class Account extends Provider implements AccountInterface {
     return super.getNonceForAddress(this.address, blockIdentifier);
   }
 
-  public async getStarkName(StarknetIdContract?: string): Promise<string | Error> {
-    const chainId = await this.getChainId();
-    const contract = StarknetIdContract ?? getStarknetIdContract(chainId);
-
-    try {
-      const hexDomain = await this.callContract({
-        contractAddress: contract,
-        entrypoint: 'address_to_domain',
-        calldata: compileCalldata({
-          address: this.address,
-        }),
-      });
-      const decimalDomain = hexDomain.result
-        .map((element) => new BN(hexToDecimalString(element)))
-        .slice(1);
-
-      const stringDomain = useDecoded(decimalDomain);
-
-      return stringDomain;
-    } catch {
-      return Error('Could not get stark name');
-    }
-  }
-
-  public async getAddressFromStarkName(
-    name: string,
-    StarknetIdContract?: string
-  ): Promise<string | Error> {
-    const chainId = await this.getChainId();
-    const contract = StarknetIdContract ?? getStarknetIdContract(chainId);
-
-    try {
-      const addressData = await this.callContract({
-        contractAddress: contract,
-        entrypoint: 'domain_to_address',
-        calldata: compileCalldata({
-          domain: [useEncoded(name.replace('.stark', '')).toString(10)],
-        }),
-      });
-
-      return addressData.result[0];
-    } catch {
-      return Error('Could not get address from stark name');
-    }
-  }
-
   public async estimateFee(
     calls: AllowArray<Call>,
     estimateFeeDetails?: EstimateFeeDetails | undefined
@@ -672,4 +623,11 @@ export class Account extends Provider implements AccountInterface {
       },
     };
   }
+
+  public override async getStarkName(
+    address: BigNumberish = this.address, // default to the wallet address
+    StarknetIdContract?: string
+  ): Promise<string> {
+    return super.getStarkName(address, StarknetIdContract);
+  }
 }
diff --git a/src/provider/default.ts b/src/provider/default.ts
index c69514f1..494925af 100644
--- a/src/provider/default.ts
+++ b/src/provider/default.ts
@@ -25,6 +25,7 @@ import { BigNumberish } from '../utils/number';
 import { ProviderInterface } from './interface';
 import { RpcProvider, RpcProviderOptions } from './rpc';
 import { SequencerProvider, SequencerProviderOptions } from './sequencer';
+import { getAddressFromStarkName, getStarkName } from './starknetId';
 import { BlockIdentifier } from './utils';
 
 export interface ProviderOptions {
@@ -207,4 +208,12 @@ export class Provider implements ProviderInterface {
   public async getStateUpdate(blockIdentifier?: BlockIdentifier): Promise<StateUpdateResponse> {
     return this.provider.getStateUpdate(blockIdentifier);
   }
+
+  public async getStarkName(address: BigNumberish, StarknetIdContract?: string): Promise<string> {
+    return getStarkName(this, address, StarknetIdContract);
+  }
+
+  public async getAddressFromStarkName(name: string, StarknetIdContract?: string): Promise<string> {
+    return getAddressFromStarkName(this, name, StarknetIdContract);
+  }
 }
diff --git a/src/provider/rpc.ts b/src/provider/rpc.ts
index 255f5ca3..8e5b6c0e 100644
--- a/src/provider/rpc.ts
+++ b/src/provider/rpc.ts
@@ -31,6 +31,7 @@ import { parseCalldata, wait } from '../utils/provider';
 import { RPCResponseParser } from '../utils/responseParser/rpc';
 import { LibraryError } from './errors';
 import { ProviderInterface } from './interface';
+import { getAddressFromStarkName, getStarkName } from './starknetId';
 import { Block, BlockIdentifier } from './utils';
 
 export type RpcProviderOptions = {
@@ -500,4 +501,12 @@ export class RpcProvider implements ProviderInterface {
   ): Promise<TransactionSimulationResponse> {
     throw new Error('RPC does not implement simulateTransaction function');
   }
+
+  public async getStarkName(address: BigNumberish, StarknetIdContract?: string): Promise<string> {
+    return getStarkName(this, address, StarknetIdContract);
+  }
+
+  public async getAddressFromStarkName(name: string, StarknetIdContract?: string): Promise<string> {
+    return getAddressFromStarkName(this, name, StarknetIdContract);
+  }
 }
diff --git a/src/provider/sequencer.ts b/src/provider/sequencer.ts
index 24f47750..66b75e7e 100644
--- a/src/provider/sequencer.ts
+++ b/src/provider/sequencer.ts
@@ -44,6 +44,7 @@ import { randomAddress } from '../utils/stark';
 import { buildUrl } from '../utils/url';
 import { GatewayError, HttpError, LibraryError } from './errors';
 import { ProviderInterface } from './interface';
+import { getAddressFromStarkName, getStarkName } from './starknetId';
 import { Block, BlockIdentifier } from './utils';
 
 type NetworkName = 'mainnet-alpha' | 'goerli-alpha' | 'goerli-alpha-2';
@@ -605,4 +606,12 @@ export class SequencerProvider implements ProviderInterface {
     const args = new Block(blockIdentifier).sequencerIdentifier;
     return this.fetchEndpoint('get_block_traces', { ...args });
   }
+
+  public async getStarkName(address: BigNumberish, StarknetIdContract?: string): Promise<string> {
+    return getStarkName(this, address, StarknetIdContract);
+  }
+
+  public async getAddressFromStarkName(name: string, StarknetIdContract?: string): Promise<string> {
+    return getAddressFromStarkName(this, name, StarknetIdContract);
+  }
 }
diff --git a/src/provider/starknetId.ts b/src/provider/starknetId.ts
new file mode 100644
index 00000000..43f6139e
--- /dev/null
+++ b/src/provider/starknetId.ts
@@ -0,0 +1,64 @@
+import { BN } from 'bn.js';
+
+import { BigNumberish, hexToDecimalString, toBN, toHex } from '../utils/number';
+import { compileCalldata } from '../utils/stark';
+import { getStarknetIdContract, useDecoded, useEncoded } from '../utils/starknetId';
+import { ProviderInterface } from './interface';
+
+export async function getStarkName(
+  provider: ProviderInterface,
+  address: BigNumberish,
+  StarknetIdContract?: string
+): Promise<string> {
+  const chainId = await provider.getChainId();
+  const contract = StarknetIdContract ?? getStarknetIdContract(chainId);
+
+  try {
+    const hexDomain = await provider.callContract({
+      contractAddress: contract,
+      entrypoint: 'address_to_domain',
+      calldata: compileCalldata({
+        address: toHex(toBN(address)),
+      }),
+    });
+    const decimalDomain = hexDomain.result
+      .map((element) => new BN(hexToDecimalString(element)))
+      .slice(1);
+
+    const stringDomain = useDecoded(decimalDomain);
+
+    if (!stringDomain) {
+      throw Error('Starkname not found');
+    }
+
+    return stringDomain;
+  } catch (e) {
+    if (e instanceof Error && e.message === 'Starkname not found') {
+      throw e;
+    }
+    throw Error('Could not get stark name');
+  }
+}
+
+export async function getAddressFromStarkName(
+  provider: ProviderInterface,
+  name: string,
+  StarknetIdContract?: string
+): Promise<string> {
+  const chainId = await provider.getChainId();
+  const contract = StarknetIdContract ?? getStarknetIdContract(chainId);
+
+  try {
+    const addressData = await provider.callContract({
+      contractAddress: contract,
+      entrypoint: 'domain_to_address',
+      calldata: compileCalldata({
+        domain: [useEncoded(name.replace('.stark', '')).toString(10)],
+      }),
+    });
+
+    return addressData.result[0];
+  } catch {
+    throw Error('Could not get address from stark name');
+  }
+}
diff --git a/src/utils/starknetId.ts b/src/utils/starknetId.ts
index bdbc6c89..b487884c 100644
--- a/src/utils/starknetId.ts
+++ b/src/utils/starknetId.ts
@@ -52,6 +52,11 @@ export function useDecoded(encoded: BN[]): string {
           : bigAlphabet[bigAlphabet.length - 1].repeat((k - 1) / 2 + 1));
     decoded += '.';
   });
+
+  if (!decoded) {
+    return decoded;
+  }
+
   return decoded.concat('stark');
 }
 
diff --git a/www/docs/API/Provider/provider.md b/www/docs/API/Provider/provider.md
index 0a8fb13c..ff1cb319 100644
--- a/www/docs/API/Provider/provider.md
+++ b/www/docs/API/Provider/provider.md
@@ -253,6 +253,30 @@ Wait for the transaction to be accepted on L2 or L1.
 
 ---
 
+### getStarkName()
+
+provider.**getStarkName**(address, StarknetIdContract) => _Promise<string | Error>_
+
+Gets starknet.id stark name with the address provided
+
+The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network.
+
+Returns directly a string (Example: `vitalik.stark`).
+
+---
+
+### getAddressFromStarkName()
+
+provider.**getAddressFromStarkName**(name, StarknetIdContract) => _Promise<string | Error>_
+
+Gets account address with the starknet id stark name.
+
+The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network.
+
+Returns directly the address in a string (Example: `0xff...34`).
+
+---
+
 ### getStateUpdate()
 
 provider.**getStateUpdate**(blockIdentifier) => _Promise < StateUpdateResponse >_
diff --git a/www/docs/API/account.md b/www/docs/API/account.md
index f76a1cee..3a4a40dd 100644
--- a/www/docs/API/account.md
+++ b/www/docs/API/account.md
@@ -525,28 +525,16 @@ The _details_ object may include any of:
 
 ### getStarkName()
 
-account.**getStarkName**(StarknetIdContract) => _Promise<string | Error>_
+account.**getStarkName**(address, StarknetIdContract) => _Promise<string | Error>_
 
-Gets starknet.id stark name with the address of the account
+Gets starknet.id stark name with the address provided, if `undefined` uses the address of the account
 
-The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network (It currently supports TESTNET 1 only).
+The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network.
 
 Returns directly a string (Example: `vitalik.stark`).
 
 ---
 
-### getAddressFromStarkName()
-
-account.**getAddressFromStarkName**(name, StarknetIdContract) => _Promise<string | Error>_
-
-Gets account address with the starknet id stark name.
-
-The _StarknetIdContract_ argument can be undefined, if it is, the function will automatically use official starknet id contracts of your network (It currently supports TESTNET 1 only).
-
-Returns directly the address in a string (Example: `0xff...34`).
-
----
-
 ### simulateTransaction()
 
 account.**simulateTransaction**(calls [ , estimateFeeDetails ]) => _Promise < TransactionSimulationResponse >_


Test patch:
diff --git a/__tests__/account.test.ts b/__tests__/account.test.ts
index e3a76292..5db03733 100644
--- a/__tests__/account.test.ts
+++ b/__tests__/account.test.ts
@@ -293,7 +293,7 @@ describe('deploy and test Wallet', () => {
       const address = await account.getAddressFromStarkName('ben.stark', namingAddress);
       expect(hexToDecimalString(address as string)).toEqual(hexToDecimalString(account.address));
 
-      const name = await account.getStarkName(namingAddress);
+      const name = await account.getStarkName(undefined, namingAddress);
       expect(name).toEqual('ben.stark');
     });
   });
diff --git a/__tests__/utils/starknetId.test.ts b/__tests__/utils/starknetId.test.ts
index 3b678328..9fbec930 100644
--- a/__tests__/utils/starknetId.test.ts
+++ b/__tests__/utils/starknetId.test.ts
@@ -28,6 +28,10 @@ describe('Should tets StarknetId utils', () => {
     }
   });
 
+  test('Should test useEncoded and useDecoded hook with an empty string', () => {
+    expect(useDecoded([])).toBe('');
+  });
+
   test('Should test useDecoded and useEncoded hook with an encoded number', () => {
     for (let index = 0; index < 2500; index += 1) {
       const decoded = useDecoded([new BN(index)]);