diff --git a/ecosystem/typescript/sdk/src/client/core.ts b/ecosystem/typescript/sdk/src/client/core.ts index d396da3fb62837..0b942b6fbc4b16 100644 --- a/ecosystem/typescript/sdk/src/client/core.ts +++ b/ecosystem/typescript/sdk/src/client/core.ts @@ -26,12 +26,14 @@ async function axiosRequest( url: string, method: "GET" | "POST", body?: Request, + mediaType?: string, params?: Record, overrides?: ClientConfig, ): Promise> { const headers: Record = { ...overrides?.HEADERS, "x-aptos-client": `aptos-ts-sdk/${VERSION}`, + "content-type": mediaType ?? "application/json", }; if (overrides?.TOKEN) { @@ -66,9 +68,9 @@ async function axiosRequest( * @returns the response or AptosApiError */ export async function aptosRequest(options: AptosRequest): Promise> { - const { url, endpoint, method, body, params, overrides } = options; + const { url, endpoint, method, body, mediaType, params, overrides } = options; const fullEndpoint = `${url}/${endpoint ?? ""}`; - const response = await axiosRequest(fullEndpoint, method, body, params, overrides); + const response = await axiosRequest(fullEndpoint, method, body, mediaType, params, overrides); const result: AptosResponse = { status: response.status, diff --git a/ecosystem/typescript/sdk/src/client/types.ts b/ecosystem/typescript/sdk/src/client/types.ts index dea89db063753b..d20388d27a43bf 100644 --- a/ecosystem/typescript/sdk/src/client/types.ts +++ b/ecosystem/typescript/sdk/src/client/types.ts @@ -14,11 +14,24 @@ export type ClientConfig = { WITH_CREDENTIALS?: boolean; }; +/** + * The API request type + * + * @param url - the url to make the request to, i.e https://fullnode.aptoslabs.devnet.com/v1 + * @param method - the request method "GET" | "POST" + * @param endpoint (optional) - the endpoint to make the request to, i.e transactions + * @param body (optional) - the body of the request + * @param mediaType (optional) - the media type, that would become the `content-type` header + * @param params (optional) - query params to add to the request + * @param originMethod (optional) - the local method the request came from + * @param overrides (optional) - a `ClientConfig` object type to override request data + */ export type AptosRequest = { url: string; method: "GET" | "POST"; endpoint?: string; body?: any; + mediaType?: string; params?: Record; originMethod?: string; overrides?: ClientConfig; diff --git a/ecosystem/typescript/sdk/src/providers/aptos_client.ts b/ecosystem/typescript/sdk/src/providers/aptos_client.ts index 4ffdbf3cdc2454..20d65ecb14c52c 100644 --- a/ecosystem/typescript/sdk/src/providers/aptos_client.ts +++ b/ecosystem/typescript/sdk/src/providers/aptos_client.ts @@ -457,7 +457,8 @@ export class AptosClient { body: signedTxn, endpoint: "transactions", originMethod: "submitSignedBCSTransaction", - overrides: { HEADERS: { "content-type": "application/x.aptos.signed_transaction+bcs" }, ...this.config }, + mediaType: "application/x.aptos.signed_transaction+bcs", + overrides: { ...this.config }, }); return data; } @@ -495,7 +496,8 @@ export class AptosClient { endpoint: "transactions/simulate", params: queryParams, originMethod: "submitBCSSimulation", - overrides: { HEADERS: { "content-type": "application/x.aptos.signed_transaction+bcs" }, ...this.config }, + mediaType: "application/x.aptos.signed_transaction+bcs", + overrides: { ...this.config }, }); return data; } @@ -735,7 +737,7 @@ export class AptosClient { endpoint: `tables/${handle}/item`, originMethod: "getTableItem", params: { ledger_version: query?.ledgerVersion?.toString() }, - overrides: { HEADERS: { "content-type": "application/json" }, ...this.config }, + overrides: { ...this.config }, }); return response.data; } @@ -1056,7 +1058,7 @@ export class AptosClient { endpoint: "view", originMethod: "getTableItem", params: { ledger_version }, - overrides: { HEADERS: { "content-type": "application/json" }, ...this.config }, + overrides: { ...this.config }, }); return data; } diff --git a/ecosystem/typescript/sdk/src/tests/e2e/client.test.ts b/ecosystem/typescript/sdk/src/tests/e2e/client.test.ts index 141579d0408c5f..cfb311aa9bba48 100644 --- a/ecosystem/typescript/sdk/src/tests/e2e/client.test.ts +++ b/ecosystem/typescript/sdk/src/tests/e2e/client.test.ts @@ -1,178 +1,235 @@ import { AptosApiError, aptosRequest } from "../../client"; import { VERSION } from "../../version"; -import { NODE_URL } from "../unit/test_helper.test"; +import { getTransaction, longTestTimeout, NODE_URL } from "../unit/test_helper.test"; -test("server response should include cookies", async () => { - try { - const response = await aptosRequest({ - // use devnet as localnet doesnt set cookies - url: "https://fullnode.devnet.aptoslabs.com/v1", - method: "GET", - originMethod: "test cookies", - }); - expect(response.headers).toHaveProperty("set-cookie"); - } catch (error: any) { - // should not get here - expect(true).toBe(false); - } -}); +test( + "server response should include cookies", + async () => { + try { + const response = await aptosRequest({ + // use devnet as localnet doesnt set cookies + url: "https://fullnode.devnet.aptoslabs.com/v1", + method: "GET", + originMethod: "test cookies", + }); + expect(response.headers).toHaveProperty("set-cookie"); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("call should include x-aptos-client header", async () => { - try { - const response = await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "accounts/0x1", - body: null, - originMethod: "test x-aptos-client header", - }); - expect(response.config.headers).toHaveProperty("x-aptos-client", `aptos-ts-sdk/${VERSION}`); - } catch (error: any) { - // should not get here - expect(true).toBe(false); - } -}); +test( + "call should include x-aptos-client header", + async () => { + try { + const response = await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "accounts/0x1", + body: null, + originMethod: "test x-aptos-client header", + }); + expect(response.config.headers).toHaveProperty("x-aptos-client", `aptos-ts-sdk/${VERSION}`); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("when token is set", async () => { - try { - const response = await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "accounts/0x1", - body: null, - originMethod: "test 200 status", - overrides: { TOKEN: "my-token" }, - }); - expect(response.config.headers).toHaveProperty("Authorization", "Bearer my-token"); - } catch (error: any) { - // should not get here - expect(true).toBe(false); - } -}); +test( + "call should include all expected headers", + async () => { + const bcsTxn = await getTransaction(); + try { + const response = await aptosRequest({ + url: `${NODE_URL}`, + method: "POST", + endpoint: "transactions", + body: bcsTxn, + originMethod: "test request includes all headers", + mediaType: "application/x.aptos.signed_transaction+bcs", + overrides: { HEADERS: { my: "header" } }, + }); + expect(response.config.headers).toHaveProperty("x-aptos-client", `aptos-ts-sdk/${VERSION}`); + expect(response.config.headers).toHaveProperty("my", "header"); + expect(response.config.headers).toHaveProperty("Content-Type", "application/x.aptos.signed_transaction+bcs"); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("when token is not set", async () => { - try { - const response = await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "accounts/0x1", - body: null, - originMethod: "test 200 status", - }); - expect(response.config.headers).not.toHaveProperty("Authorization", "Bearer my-token"); - } catch (error: any) { - // should not get here - expect(true).toBe(false); - } -}); +test( + "when token is set", + async () => { + try { + const response = await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "accounts/0x1", + body: null, + originMethod: "test 200 status", + overrides: { TOKEN: "my-token" }, + }); + expect(response.config.headers).toHaveProperty("Authorization", "Bearer my-token"); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("when server returns 400 status code", async () => { - try { - await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "transactions/by_hash/0x123", - body: null, - originMethod: "test 400 status", - }); - } catch (error: any) { - expect(error).toBeInstanceOf(AptosApiError); - expect(error.url).toBe(`${NODE_URL}/transactions/by_hash/0x123`); - expect(error.status).toBe(400); - expect(error.statusText).toBe("Bad Request"); - expect(error.data).toEqual({ - message: 'failed to parse path `txn_hash`: failed to parse "string(HashValue)": unable to parse HashValue', - error_code: "web_framework_error", - vm_error_code: null, - }); - expect(error.request).toEqual({ - url: `${NODE_URL}`, - method: "GET", - originMethod: "test 400 status", - endpoint: "transactions/by_hash/0x123", - body: null, - }); - } -}); +test( + "when token is not set", + async () => { + try { + const response = await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "accounts/0x1", + body: null, + originMethod: "test 200 status", + }); + expect(response.config.headers).not.toHaveProperty("Authorization", "Bearer my-token"); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("when server returns 200 status code", async () => { - try { - const response = await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "accounts/0x1", - body: null, - originMethod: "test 200 status", - }); - expect(response).toHaveProperty("data", { - sequence_number: "0", - authentication_key: "0x0000000000000000000000000000000000000000000000000000000000000001", - }); - } catch (error: any) { - // should not get here - expect(true).toBe(false); - } -}); +test( + "when server returns 400 status code", + async () => { + try { + await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "transactions/by_hash/0x123", + body: null, + originMethod: "test 400 status", + }); + } catch (error: any) { + expect(error).toBeInstanceOf(AptosApiError); + expect(error.url).toBe(`${NODE_URL}/transactions/by_hash/0x123`); + expect(error.status).toBe(400); + expect(error.statusText).toBe("Bad Request"); + expect(error.data).toEqual({ + message: 'failed to parse path `txn_hash`: failed to parse "string(HashValue)": unable to parse HashValue', + error_code: "web_framework_error", + vm_error_code: null, + }); + expect(error.request).toEqual({ + url: `${NODE_URL}`, + method: "GET", + originMethod: "test 400 status", + endpoint: "transactions/by_hash/0x123", + body: null, + }); + } + }, + longTestTimeout, +); -test("when server returns 404 status code", async () => { - try { - await aptosRequest({ - url: `${NODE_URL}`, - method: "GET", - endpoint: "transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea", - body: null, - originMethod: "test 404 status", - }); - } catch (error: any) { - expect(error).toBeInstanceOf(AptosApiError); - expect(error.url).toBe( - `${NODE_URL}/transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea`, - ); - expect(error.status).toBe(404); - expect(error.statusText).toBe("Not Found"); - expect(error.data).toEqual({ - message: - "Transaction not found by Transaction hash(0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea)", - error_code: "transaction_not_found", - vm_error_code: null, - }); - expect(error.request).toEqual({ - url: `${NODE_URL}`, - method: "GET", - originMethod: "test 404 status", - endpoint: "transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea", - body: null, - }); - } -}); +test( + "when server returns 200 status code", + async () => { + try { + const response = await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "accounts/0x1", + body: null, + originMethod: "test 200 status", + }); + expect(response).toHaveProperty("data", { + sequence_number: "0", + authentication_key: "0x0000000000000000000000000000000000000000000000000000000000000001", + }); + } catch (error: any) { + // should not get here + expect(true).toBe(false); + } + }, + longTestTimeout, +); -test("when server returns transaction submission error", async () => { - try { - await aptosRequest({ - url: `${NODE_URL}`, - method: "POST", - endpoint: "transactions", - body: new Uint8Array([1, 2, 3]), - originMethod: "test transaction submission error", - overrides: { HEADERS: { "content-type": "application/x.aptos.signed_transaction+bcs" } }, - }); - } catch (error: any) { - expect(error).toBeInstanceOf(AptosApiError); - expect(error.url).toBe(`${NODE_URL}/transactions`); - expect(error.status).toBe(400); - expect(error.statusText).toBe("Bad Request"); - expect(error.data).toEqual({ - message: "Failed to deserialize input into SignedTransaction: unexpected end of input", - error_code: "invalid_input", - vm_error_code: null, - }); - expect(error.request).toEqual({ - url: `${NODE_URL}`, - method: "POST", - originMethod: "test transaction submission error", - endpoint: "transactions", - body: new Uint8Array([1, 2, 3]), - overrides: { HEADERS: { "content-type": "application/x.aptos.signed_transaction+bcs" } }, - }); - } -}); +test( + "when server returns 404 status code", + async () => { + try { + await aptosRequest({ + url: `${NODE_URL}`, + method: "GET", + endpoint: "transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea", + body: null, + originMethod: "test 404 status", + }); + } catch (error: any) { + expect(error).toBeInstanceOf(AptosApiError); + expect(error.url).toBe( + `${NODE_URL}/transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea`, + ); + expect(error.status).toBe(404); + expect(error.statusText).toBe("Not Found"); + expect(error.data).toEqual({ + message: + "Transaction not found by Transaction hash(0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea)", + error_code: "transaction_not_found", + vm_error_code: null, + }); + expect(error.request).toEqual({ + url: `${NODE_URL}`, + method: "GET", + originMethod: "test 404 status", + endpoint: "transactions/by_hash/0x23851af73879128b541bafad4b49d0b6f1ac0d49ed2400632d247135fbca7bea", + body: null, + }); + } + }, + longTestTimeout, +); + +test( + "when server returns transaction submission error", + async () => { + try { + await aptosRequest({ + url: `${NODE_URL}`, + method: "POST", + endpoint: "transactions", + body: new Uint8Array([1, 2, 3]), + originMethod: "test transaction submission error", + mediaType: "application/x.aptos.signed_transaction+bcs", + }); + } catch (error: any) { + expect(error).toBeInstanceOf(AptosApiError); + expect(error.url).toBe(`${NODE_URL}/transactions`); + expect(error.status).toBe(400); + expect(error.statusText).toBe("Bad Request"); + expect(error.data).toEqual({ + message: "Failed to deserialize input into SignedTransaction: unexpected end of input", + error_code: "invalid_input", + vm_error_code: null, + }); + expect(error.request).toEqual({ + url: `${NODE_URL}`, + method: "POST", + originMethod: "test transaction submission error", + endpoint: "transactions", + body: new Uint8Array([1, 2, 3]), + mediaType: "application/x.aptos.signed_transaction+bcs", + }); + } + }, + longTestTimeout, +); diff --git a/ecosystem/typescript/sdk/src/tests/unit/test_helper.test.ts b/ecosystem/typescript/sdk/src/tests/unit/test_helper.test.ts index 19d38be1a91bf7..9b4cacd7a72c7b 100644 --- a/ecosystem/typescript/sdk/src/tests/unit/test_helper.test.ts +++ b/ecosystem/typescript/sdk/src/tests/unit/test_helper.test.ts @@ -1,5 +1,9 @@ +import { AptosAccount } from "../../account"; +import { bcsToBytes, bcsSerializeUint64 } from "../../bcs"; import { ClientConfig } from "../../client"; import { FaucetClient } from "../../plugins/faucet_client"; +import { AptosClient } from "../../providers"; +import { TxnBuilderTypes } from "../../transaction_builder"; import { CustomEndpoints } from "../../utils/api-endpoints"; export const NODE_URL = process.env.APTOS_NODE_URL!; @@ -25,6 +29,30 @@ export function getFaucetClient(): FaucetClient { return new FaucetClient(NODE_URL, FAUCET_URL, config); } +export async function getTransaction(): Promise { + const client = new AptosClient(NODE_URL); + const faucetClient = getFaucetClient(); + + const account1 = new AptosAccount(); + await faucetClient.fundAccount(account1.address(), 100_000_000); + + const account2 = new AptosAccount(); + + const entryFunctionPayload = new TxnBuilderTypes.TransactionPayloadEntryFunction( + TxnBuilderTypes.EntryFunction.natural( + "0x1::aptos_account", + "transfer", + [], + [bcsToBytes(TxnBuilderTypes.AccountAddress.fromHex(account2.address())), bcsSerializeUint64(717)], + ), + ); + + const rawTxn = await client.generateRawTransaction(account1.address(), entryFunctionPayload); + + const bcsTxn = AptosClient.generateBCSTransaction(account1, rawTxn); + return bcsTxn; +} + test("noop", () => { // All TS files are compiled by default into the npm package // Adding this empty test allows us to: