Repository: 0xs34n/starknet.js
Pull request #282 (2022-08-08 13:56:53)

fix(sequenceProvider): feederGatewayUrl and gatewayUrl
Currently, the way feederGatewayUrl and gatewayUrl that are being assigned value is wrong.

```
this.feederGatewayUrl =
        optionsOrProvider.feederGatewayUrl ?? urljoin(this.baseUrl, 'feeder_gateway');
```

This won't work if `feederGatewayUrl='feeder_gateway'` as described in the [docs](https://www.starknetjs.com/docs/api/provider/#creating-an-instance)

It will fail with the following error when making contract calls:

```Error: Could not POST from endpoint `feeder_gateway/call_contract?blockNumber=pending`: Only absolute URLs are supported``` 

This PR aims to fix this bug by taking into account that feederGatewayUrl can be a url. for eg: `https://alpha4.starknet.io/feeder_gateway` or just a path which can be concatenated with baseUrl.. for eg: `feeder_gateway`


Patch:
diff --git a/src/index.ts b/src/index.ts
index bbec6f06..fd646039 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -22,3 +22,4 @@ export * as uint256 from './utils/uint256';
 export * as shortString from './utils/shortString';
 export * as typedData from './utils/typedData';
 export * from './utils/address';
+export * from './utils/url';
diff --git a/src/provider/sequencer.ts b/src/provider/sequencer.ts
index ff00a84c..77318e06 100644
--- a/src/provider/sequencer.ts
+++ b/src/provider/sequencer.ts
@@ -31,6 +31,7 @@ import { BigNumberish, bigNumberishArrayToDecimalStringArray, toBN, toHex } from
 import { parseContract, wait } from '../utils/provider';
 import { SequencerAPIResponseParser } from '../utils/responseParser/sequencer';
 import { randomAddress } from '../utils/stark';
+import { buildUrl } from '../utils/url';
 import { GatewayError, HttpError } from './errors';
 import { ProviderInterface } from './interface';
 import { BlockIdentifier, getFormattedBlockIdentifier } from './utils';
@@ -74,9 +75,13 @@ export class SequencerProvider implements ProviderInterface {
       this.gatewayUrl = urljoin(this.baseUrl, 'gateway');
     } else {
       this.baseUrl = optionsOrProvider.baseUrl;
-      this.feederGatewayUrl =
-        optionsOrProvider.feederGatewayUrl ?? urljoin(this.baseUrl, 'feeder_gateway');
-      this.gatewayUrl = optionsOrProvider.gatewayUrl ?? urljoin(this.baseUrl, 'gateway');
+      this.feederGatewayUrl = buildUrl(
+        this.baseUrl,
+        'feeder_gateway',
+        optionsOrProvider.feederGatewayUrl
+      );
+      this.gatewayUrl = buildUrl(this.baseUrl, 'gateway', optionsOrProvider.gatewayUrl);
+
       this.chainId =
         optionsOrProvider.chainId ??
         SequencerProvider.getChainIdFromBaseUrl(optionsOrProvider.baseUrl);
diff --git a/src/utils/address.ts b/src/utils/address.ts
index 4e76b5d7..425ab2c4 100644
--- a/src/utils/address.ts
+++ b/src/utils/address.ts
@@ -3,7 +3,7 @@ import { arrayify } from '@ethersproject/bytes';
 
 import { MASK_251, ZERO } from '../constants';
 import { addHexPrefix, removeHexPrefix } from './encode';
-import { keccakBn } from './hash';
+import { pedersen } from './hash';
 import { BigNumberish, assertInRange, toBN, toHex } from './number';
 
 export function addAddressPadding(address: BigNumberish): string {
@@ -25,7 +25,7 @@ export function validateAndParseAddress(address: BigNumberish): string {
 // from https://github.com/ethers-io/ethers.js/blob/fc1e006575d59792fa97b4efb9ea2f8cca1944cf/packages/address/src.ts/index.ts#L12
 export function getChecksumAddress(address: BigNumberish): string {
   const chars = removeHexPrefix(validateAndParseAddress(address)).toLowerCase().split('');
-  const hashed = arrayify(keccakBn(address), { hexPad: 'left' }); // in case the hash is 251 bits (63 chars) we need to pad it to 64 chars without changing the number value ("left")
+  const hashed = arrayify(pedersen([0, address]), { hexPad: 'left' }); // as the hash will be 251 bits (63 chars) we need to pad it to 64 chars without changing the number value ("left")
 
   for (let i = 0; i < chars.length; i += 2) {
     if (hashed[i >> 1] >> 4 >= 8) {
diff --git a/src/utils/hash.ts b/src/utils/hash.ts
index 3c604137..5bcf02c6 100644
--- a/src/utils/hash.ts
+++ b/src/utils/hash.ts
@@ -1,6 +1,5 @@
 import BN from 'bn.js';
 import { keccak256 } from 'ethereum-cryptography/keccak';
-import { hexToBytes } from 'ethereum-cryptography/utils';
 import assert from 'minimalistic-assert';
 
 import {
@@ -14,18 +13,12 @@ import {
 } from '../constants';
 import { RawCalldata } from '../types/lib';
 import { ec } from './ellipticCurve';
-import { addHexPrefix, buf2hex, removeHexPrefix, utf8ToArray } from './encode';
+import { addHexPrefix, buf2hex, utf8ToArray } from './encode';
 import { BigNumberish, toBN, toFelt, toHex } from './number';
 
 export const transactionVersion = 0;
 export const feeTransactionVersion = toBN(2).pow(toBN(128)).add(toBN(transactionVersion));
 
-export function keccakBn(value: BigNumberish): string {
-  const hexWithoutPrefix = removeHexPrefix(toHex(toBN(value)));
-  const evenHex = hexWithoutPrefix.length % 2 === 0 ? hexWithoutPrefix : `0${hexWithoutPrefix}`;
-  return addHexPrefix(buf2hex(keccak256(hexToBytes(evenHex))));
-}
-
 function keccakHex(value: string): string {
   return addHexPrefix(buf2hex(keccak256(utf8ToArray(value))));
 }
diff --git a/src/utils/url.ts b/src/utils/url.ts
new file mode 100644
index 00000000..adff801e
--- /dev/null
+++ b/src/utils/url.ts
@@ -0,0 +1,53 @@
+import urljoin from 'url-join';
+
+/**
+ * Inspired from https://github.com/segmentio/is-url
+ */
+
+/**
+ * RegExps.
+ * A URL must match #1 and then at least one of #2/#3.
+ * Use two levels of REs to avoid REDOS.
+ */
+const protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/;
+
+const localhostDomainRE = /^localhost[:?\d]*(?:[^:?\d]\S*)?$/;
+const nonLocalhostDomainRE = /^[^\s.]+\.\S{2,}$/;
+
+/**
+ * Loosely validate a URL `string`.
+ * @param {String} s
+ * @return {Boolean}
+ */
+export function isUrl(s?: string): boolean {
+  if (!s) {
+    return false;
+  }
+
+  if (typeof s !== 'string') {
+    return false;
+  }
+
+  const match = s.match(protocolAndDomainRE);
+  if (!match) {
+    return false;
+  }
+
+  const everythingAfterProtocol = match[1];
+  if (!everythingAfterProtocol) {
+    return false;
+  }
+
+  if (
+    localhostDomainRE.test(everythingAfterProtocol) ||
+    nonLocalhostDomainRE.test(everythingAfterProtocol)
+  ) {
+    return true;
+  }
+
+  return false;
+}
+
+export function buildUrl(baseUrl: string, defaultPath: string, urlOrPath?: string) {
+  return isUrl(urlOrPath) ? urlOrPath! : urljoin(baseUrl, urlOrPath ?? defaultPath);
+}
diff --git a/www/docs/API/provider.md b/www/docs/API/provider.md
index 8c5b1e12..f5be8261 100644
--- a/www/docs/API/provider.md
+++ b/www/docs/API/provider.md
@@ -17,19 +17,40 @@ The options for the provider depends from the network. The structure of the opti
 - options.**sequencer** - Options for sequencer provider
 - options.**rpc** - Options for RPC provider
 
-Example:
+The easiest way to get started is:
 
+```typescript
+const provider = new starknet.Provider()
 ```
+
+The above snippet creates a Starknet Provider instance with `goerli-alpha` network.
+
+However, if you want to use `mainnet-alpha` or explicitly declare the network, you can use:
+
+```typescript
 const provider = new starknet.Provider({
   sequencer: {
-    baseUrl: 'https://alpha4.starknet.io',
-    feederGatewayUrl: 'feeder_gateway',
-    gatewayUrl: 'gateway',
+    network: 'mainnet-alpha' // or 'goerli-alpha'
+  }
+ })
+```
+
+If you want to more control:
+
+```typescript
+const provider = new starknet.Provider({
+  sequencer: {
+  baseUrl: 'https://alpha4.starknet.io',
+  feederGatewayUrl: 'feeder_gateway',
+  gatewayUrl: 'gateway',
   }
 })
+
 ```
 
-**This is also default options for the constructor for the **testnet\*\*\*
+\*\*This is also default options for the Provider constructor with `network: 'goerli-alpha'`\*\*\*
+
+\*\*Note: `network` arguement should work in most cases. If you want to use sequencer arguement with `baseUrl`, you will not be able to use `network` field in the object.\*\*\*
 
 ## Methods
 
@@ -47,10 +68,12 @@ The call object structure:
 
 ###### CallContractResponse
 
-```
+```typescript
+
 {
-    result: string[];
+  result: string[];
 }
+
 ```
 
 <hr/>
@@ -61,7 +84,8 @@ Gets the block information.
 
 ###### _GetBlockResponse_
 
-```
+```typescript
+
 {
   accepted_time: number;
   block_hash: string;
@@ -75,6 +99,7 @@ Gets the block information.
   transactions: Array<string>;
   starknet_version?: string;
 }
+
 ```
 
 <hr/>
@@ -85,12 +110,14 @@ Gets the contract class of the deployed contract.
 
 ###### _ContractClass_
 
-```
+```typescript
+
 {
   program: CompressedProgram;
   entry_points_by_type: EntryPointsByType;
   abi?: Abi;
 }
+
 ```
 
 <hr/>
@@ -107,7 +134,8 @@ Gets the status of a transaction.
 
 ###### _GetTransactionReceiptResponse_
 
-```
+```typescript
+
 {
   transaction_hash: string;
   status: 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED';
@@ -117,6 +145,7 @@ Gets the status of a transaction.
   events?: Array<Event>;
   l1_origin_message?: MessageToL2;
 }
+
 ```
 
 <hr/>
@@ -127,7 +156,8 @@ Gets the transaction information from a tx hash.
 
 ###### _GetTransactionResponse_
 
-```
+```typescript
+
 {
   transaction_hash: string;
   version?: string;
@@ -140,6 +170,7 @@ Gets the transaction information from a tx hash.
   contract_class?: ContractClass;
   sender_address?: string;
 }
+
 ```
 
 <hr/>
@@ -150,7 +181,8 @@ Declares a contract on Starknet
 
 ###### _DeclareContractResponse_
 
-```
+```typescript
+
 {
   transaction_hash: string;
   class_hash: string;
@@ -166,11 +198,13 @@ Deploys a contract on Starknet
 
 ###### _DeployContractResponse_
 
-```
+```typescript
+
 {
   transaction_hash: string;
   contract_address?: string;
 };
+
 ```
 
 <hr/>
@@ -197,12 +231,14 @@ or
 
 Example:
 
-```
+```typescript
+
 const provider = new starknet.Provider({
   baseUrl: 'https://alpha4.starknet.io',
   feederGatewayUrl: 'feeder_gateway',
   gatewayUrl: 'gateway',
 })
+
 ```
 
 ## Methods
@@ -211,11 +247,13 @@ Gets the smart contract address on the network
 
 provider.**getContractAddresses**() => _Promise < GetContractAddressesResponse >_
 
-```
+```typescript
+
 {
   Starknet: string;
   GpsStatementVerifier: string;
 }
+
 ```
 
 <hr/>
@@ -226,7 +264,8 @@ Gets the status of a transaction.
 
 ###### _GetTransactionStatusResponse_
 
-```
+```typescript
+
 {
   tx_status: 'NOT_RECEIVED' | 'RECEIVED' | 'PENDING' | 'ACCEPTED_ON_L2' | 'ACCEPTED_ON_L1' | 'REJECTED';
   block_hash: string;
@@ -236,6 +275,7 @@ Gets the status of a transaction.
     error_message: string;
   }
 }
+
 ```
 
 <hr/>
@@ -246,7 +286,7 @@ Gets the transaction trace from a tx hash.
 
 ###### _GetTransactionTraceResponse_
 
-```
+```typescript
 {
   function_invocation: {
     caller_address: string;
@@ -254,16 +294,17 @@ Gets the transaction trace from a tx hash.
     code_address: string;
     selector: string;
     calldata: {
-        [inputName: string]: string | string[] | { type: 'struct'; [k: string]: BigNumberish };
+      [inputName: string]: string | string[] | { type: 'struct'; [k: string]: BigNumberish };
     };
     result: Array<any>;
     execution_resources: any;
     internal_call: Array<any>;
     events: Array<any>;
     messages: Array<any>;
-  };
+    };
   signature: Signature;
 }
+
 ```
 
 # RpcProvider
@@ -276,10 +317,12 @@ Gets the transaction trace from a tx hash.
 
 Example:
 
-```
+```typescript
+
 const provider = new starknet.RpcProvider({
   nodeUrl: 'URL_TO_STARKNET_RPC_NODE',
 })
+
 ```
 
 ## Methods
@@ -302,7 +345,8 @@ Gets syncing status of the node
 
 ###### GetSyncingStatsResponse
 
-```
+```typescript
+
 boolean |
 {
   starting_block_hash: string;
@@ -312,6 +356,7 @@ boolean |
   highest_block_hash: string;
   highest_block_num: string;
 }
+
 ```
 
 <hr/>
@@ -320,7 +365,8 @@ provider.**getEvents**(eventFilter) => _Promise < GetEventsResponse >_
 
 ##### EventFilter
 
-```
+```typescript
+
 type EventFilter = {
   fromBlock: string;
   toBlock: string;
@@ -329,14 +375,25 @@ type EventFilter = {
   page_size: number;
   page_number: number;
 };
+
 ```
 
 ###### GetSyncingStatsResponse
 
-```
+```typescript
+
 {
   events: StarknetEmittedEvent[];
   page_number: number;
   is_last_page: number;
 }
+
+```
+
+```
+
+```
+
+```
+
 ```
diff --git a/www/guides/account.md b/www/guides/account.md
index bec18e19..f0b55466 100644
--- a/www/guides/account.md
+++ b/www/guides/account.md
@@ -14,6 +14,7 @@ Install the latest version of starknet with `npm install starknet@next`
 
 ```javascript
 import fs from "fs";
+import fs from "fs";
 import {
   Account,
   Contract,
@@ -41,31 +42,12 @@ Deploy the Account contract and wait for it to be verified on StarkNet.
 const compiledAccount = json.parse(
   fs.readFileSync("./Account.json").toString("ascii")
 );
-```
-
-> **Note** 
-> 
-> below example uses [Argent's](https://github.com/argentlabs/argent-contracts-starknet/blob/develop/contracts/ArgentAccount.cairo) account contract 
-
-```javascript
 const accountResponse = await defaultProvider.deployContract({
   contract: compiledAccount,
   addressSalt: starkKeyPub,
 });
 ```
 
-> **Note** 
-> 
-> below example uses [OpenZeppelin's](https://github.com/OpenZeppelin/cairo-contracts/blob/main/src/openzeppelin/account/presets/Account.cairo) account contract 
-
-```javascript
-const accountResponse = await defaultProvider.deployContract({
-    contract: compiledAccount,
-    constructorCalldata: [starkKeyPub],
-    addressSalt: starkKeyPub,
-});
-```
-
 ## Use your new account
 
 Wait for the deployment transaction to be accepted and assign the address of the deployed account to the Account object.
@@ -74,11 +56,7 @@ Wait for the deployment transaction to be accepted and assign the address of the
 await defaultProvider.waitForTransaction(accountResponse.transaction_hash);
 ```
 
-Once account contract is deployed [Account](../docs/API/account.md) instance can be created. Use your new account instance to sign transactions, messages or verify signatures! 
-
-> **Note**
-> 
-> Make sure your Account has enough funds to execute invocations. Use this [faucet](https://faucet.goerli.starknet.io/) for funding on testnet.
+Once account contract is deployed [Account](../docs/API/account.md) instance can be created. Use your new account instance to sign transactions, messages or verify signatures! Make sure your Account has enough funds to execute invocations.
 
 ```js
 const account = new Account(


Test patch:
diff --git a/__tests__/sequencerProvider.test.ts b/__tests__/sequencerProvider.test.ts
index 72c3e5a8..766c13c1 100644
--- a/__tests__/sequencerProvider.test.ts
+++ b/__tests__/sequencerProvider.test.ts
@@ -1,4 +1,5 @@
-import { SequencerProvider } from '../src';
+import { Contract, Provider, SequencerProvider, stark } from '../src';
+import { toBN } from '../src/utils/number';
 import {
   compiledErc20,
   describeIfNotDevnet,
@@ -8,9 +9,17 @@ import {
 
 describeIfSequencer('SequencerProvider', () => {
   let provider: SequencerProvider;
+  let customSequencerProvider: Provider;
 
   beforeAll(async () => {
     provider = getTestProvider() as SequencerProvider;
+    customSequencerProvider = new Provider({
+      sequencer: {
+        baseUrl: 'https://alpha4.starknet.io',
+        feederGatewayUrl: 'feeder_gateway',
+        gatewayUrl: 'gateway',
+      }, // Similar to arguements used in docs
+    });
   });
 
   describe('Gateway specific methods', () => {
@@ -42,4 +51,25 @@ describeIfSequencer('SequencerProvider', () => {
       });
     });
   });
+
+  describe('Test calls with Custom Sequencer Provider', () => {
+    let erc20: Contract;
+    const wallet = stark.randomAddress();
+
+    beforeAll(async () => {
+      const { contract_address, transaction_hash } = await customSequencerProvider.deployContract({
+        contract: compiledErc20,
+      });
+
+      await customSequencerProvider.waitForTransaction(transaction_hash);
+      erc20 = new Contract(compiledErc20.abi, contract_address, customSequencerProvider);
+    });
+
+    test('Check ERC20 balance using Custom Sequencer Provider', async () => {
+      const result = await erc20.balance_of(wallet);
+      const [res] = result;
+      expect(res).toStrictEqual(toBN(0));
+      expect(res).toStrictEqual(result.res);
+    });
+  });
 });
diff --git a/__tests__/utils/address.test.ts b/__tests__/utils/address.test.ts
index df8c48db..b7d0b2b1 100644
--- a/__tests__/utils/address.test.ts
+++ b/__tests__/utils/address.test.ts
@@ -26,12 +26,12 @@ describe('address checksums', () => {
       '0x2fd23d9182193775423497fc0c472e156c57c69e4089a1967fb288a2d84e914'
     );
     expect(checksumAddress).toEqual(
-      '0x02Fd23d9182193775423497fc0c472E156C57C69E4089A1967fb288A2d84e914'
+      '0x02FD23D9182193775423497Fc0c472E156C57C69E4089a1967fb288a2D84e914'
     );
   });
   test('should be able to verify checksum address', () => {
     const isValid = validateChecksumAddress(
-      '0x02Fd23d9182193775423497fc0c472E156C57C69E4089A1967fb288A2d84e914'
+      '0x02FD23D9182193775423497Fc0c472E156C57C69E4089a1967fb288a2D84e914'
     );
     expect(isValid).toEqual(true);
   });