Skip to content

Commit

Permalink
feat: add public sequencer fetch method
Browse files Browse the repository at this point in the history
  • Loading branch information
penovicp committed Jan 19, 2023
1 parent 58556ba commit 1dab230
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 34 deletions.
41 changes: 38 additions & 3 deletions __tests__/sequencerProvider.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Contract, Provider, SequencerProvider, stark } from '../src';
import { Contract, GatewayError, HttpError, Provider, SequencerProvider, stark } from '../src';
import * as fetchModule from '../src/utils/fetchPonyfill';
import { stringify } from '../src/utils/json';
import { toBigInt } from '../src/utils/number';
import { encodeShortString } from '../src/utils/shortString';
import {
Expand All @@ -14,10 +16,42 @@ import {
describeIfSequencer('SequencerProvider', () => {
const sequencerProvider = getTestProvider() as SequencerProvider;
const account = getTestAccount(sequencerProvider);
let customSequencerProvider: Provider;
let exampleContractAddress: string;

describe('Generic fetch', () => {
const fetchSpy = jest.spyOn(fetchModule, 'default');
const generateMockResponse = (ok: boolean, text: any): any => ({
ok,
text: async () => text,
});

afterAll(() => {
fetchSpy.mockRestore();
});

test('fetch unexpected error', async () => {
fetchSpy.mockResolvedValueOnce(generateMockResponse(false, null));
expect(sequencerProvider.fetch('')).rejects.toThrow(/^Could not GET from endpoint/);
});

test('fetch http error', async () => {
fetchSpy.mockResolvedValueOnce(generateMockResponse(false, 'wrong'));
expect(sequencerProvider.fetch('')).rejects.toThrow(HttpError);
});

test('fetch gateway error', async () => {
fetchSpy.mockResolvedValueOnce(generateMockResponse(false, stringify({})));
expect(sequencerProvider.fetch('')).rejects.toThrow(GatewayError);
});

test('fetch success', async () => {
fetchSpy.mockResolvedValueOnce(generateMockResponse(true, stringify({ success: '' })));
expect(sequencerProvider.fetch('')).resolves.toHaveProperty('success');
});
});

describe('Gateway specific methods', () => {
let exampleContractAddress: string;

let exampleTransactionHash: string;

beforeAll(async () => {
Expand Down Expand Up @@ -93,6 +127,7 @@ describeIfSequencer('SequencerProvider', () => {
});

describeIfDevnet('Test calls with Custom Devnet Sequencer Provider', () => {
let customSequencerProvider: Provider;
let erc20: Contract;
const wallet = stark.randomAddress();

Expand Down
72 changes: 41 additions & 31 deletions src/provider/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ import { GatewayError, HttpError, LibraryError } from './errors';
import { ProviderInterface } from './interface';
import { Block, BlockIdentifier } from './utils';

type NetworkName = 'mainnet-alpha' | 'goerli-alpha' | 'goerli-alpha-2';
export type NetworkName = 'mainnet-alpha' | 'goerli-alpha' | 'goerli-alpha-2';
export type SequencerHttpMethod = 'POST' | 'GET';

export type SequencerProviderOptions = {
headers?: object;
headers?: Record<string, string>;
blockIdentifier?: BlockIdentifier;
} & (
| {
Expand Down Expand Up @@ -84,14 +85,14 @@ export class SequencerProvider implements ProviderInterface {

public gatewayUrl: string;

public headers: object | undefined;
public headers?: Record<string, string>;

private blockIdentifier: BlockIdentifier;

private chainId: StarknetChainId;

private responseParser = new SequencerAPIResponseParser();

private blockIdentifier: BlockIdentifier;

constructor(optionsOrProvider: SequencerProviderOptions = defaultOptions) {
if ('network' in optionsOrProvider) {
this.baseUrl = SequencerProvider.getNetworkFromName(optionsOrProvider.network);
Expand Down Expand Up @@ -177,7 +178,7 @@ export class SequencerProvider implements ProviderInterface {
return `?${queryString}`;
}

private getHeaders(method: 'POST' | 'GET'): object | undefined {
private getHeaders(method: SequencerHttpMethod): Record<string, string> | undefined {
if (method === 'POST') {
return {
'Content-Type': 'application/json',
Expand All @@ -202,43 +203,52 @@ export class SequencerProvider implements ProviderInterface {
const baseUrl = this.getFetchUrl(endpoint);
const method = this.getFetchMethod(endpoint);
const queryString = this.getQueryString(query);
const headers = this.getHeaders(method);
const url = urljoin(baseUrl, endpoint, queryString);

return this.fetch(url, {
method,
body: request,
});
}

public async fetch(
endpoint: string,
options?: {
method?: SequencerHttpMethod;
body?: any;
parseAlwaysAsBigInt?: boolean;
}
): Promise<any> {
const url = buildUrl(this.baseUrl, '', endpoint);
const method = options?.method ?? 'GET';
const headers = this.getHeaders(method);

try {
const res = await fetch(url, {
const response = await fetch(url, {
method,
body: stringify(request),
headers: headers as Record<string, string>,
body: stringify(options?.body),
headers,
});
const textResponse = await res.text();
if (!res.ok) {
// This will allow user to handle contract errors
const textResponse = await response.text();

if (!response.ok) {
// This will allow the user to handle contract errors
let responseBody: any;
try {
responseBody = parse(textResponse);
} catch {
// if error parsing fails, return an http error
throw new HttpError(res.statusText, res.status);
throw new HttpError(response.statusText, response.status);
}

const errorCode = responseBody.code || ((responseBody as any)?.status_code as string); // starknet-devnet uses status_code instead of code; They need to fix that
throw new GatewayError(responseBody.message, errorCode); // Caught locally, and re-thrown for the user
throw new GatewayError(responseBody.message, responseBody.code);
}

if (endpoint === 'estimate_fee') {
return parseAlwaysAsBig(textResponse);
}
return parse(textResponse) as Sequencer.Endpoints[T]['RESPONSE'];
} catch (err) {
// rethrow custom errors
if (err instanceof GatewayError || err instanceof HttpError) {
throw err;
}
if (err instanceof Error) {
throw Error(`Could not ${method} from endpoint \`${url}\`: ${err.message}`);
}
throw err;
const parseChoice = options?.parseAlwaysAsBigInt ? parseAlwaysAsBig : parse;
return parseChoice(textResponse);
} catch (error) {
if (error instanceof Error && !(error instanceof LibraryError))
throw Error(`Could not ${method} from endpoint \`${url}\`: ${error.message}`);

throw error;
}
}

Expand Down

0 comments on commit 1dab230

Please sign in to comment.