From 15e238af9c9f43c490224f09f92528d581215435 Mon Sep 17 00:00:00 2001 From: Oliver He Date: Fri, 1 Nov 2024 14:09:00 -0400 Subject: [PATCH] Throw error with descriptive message when a Keyless Signature is invalid due to JWK rotation (#541) * add this * update * add check * update * update * update * update * fix * update * update * update iport * update changelog --- CHANGELOG.md | 1 + src/account/AbstractKeylessAccount.ts | 111 +- src/account/MultiKeyAccount.ts | 23 +- src/client/core.ts | 12 +- src/client/get.ts | 3 +- src/client/index.ts | 1 - src/client/post.ts | 3 +- src/client/types.ts | 138 -- src/core/crypto/keyless.ts | 116 +- src/core/hex.ts | 2 + src/internal/account.ts | 12 +- src/internal/keyless.ts | 46 +- src/internal/transaction.ts | 3 +- src/internal/transactionSubmission.ts | 50 +- src/transactions/authenticator/transaction.ts | 20 + src/types/error.ts | 381 +++++ src/types/index.ts | 1494 +---------------- src/types/keyless.ts | 19 + src/types/types.ts | 1491 ++++++++++++++++ tests/e2e/api/keyless.test.ts | 15 +- tests/unit/client.test.ts | 2 +- 21 files changed, 2245 insertions(+), 1698 deletions(-) delete mode 100644 src/client/types.ts create mode 100644 src/types/error.ts create mode 100644 src/types/types.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e5e12fd52..38bd2c24a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T - [`Breaking`] Updated `AccountAddress.fromString` and `AccountAddress.from` to only accept SHORT strings that are 60-64 characters long by default (with the exception of special addresses). This can be adjusted using `maxMissingChars` which is set to `4` by default. If you would like to keep the previous behavior, set `maxMissingChars` to `63` for relaxed parsing. - Add support for AIP-80 compliant private key imports and exports through `toAIP80String` - Add `PrivateKey` helpers for AIP-80: `PrivateKey.parseHexInput`, `PrivateKey.formatPrivateKey`, and `PrivateKey.AIP80_PREFIXES`. +- Adds explicit error handling Keyless accounts using `KeylessError`. Handles JWK rotations. # 1.31.0 (2024-10-24) diff --git a/src/account/AbstractKeylessAccount.ts b/src/account/AbstractKeylessAccount.ts index 583e620e7..73e0fe26a 100644 --- a/src/account/AbstractKeylessAccount.ts +++ b/src/account/AbstractKeylessAccount.ts @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 import EventEmitter from "eventemitter3"; -import { EphemeralCertificateVariant, HexInput, SigningScheme } from "../types"; +import { jwtDecode } from "jwt-decode"; +import { EphemeralCertificateVariant, HexInput, KeylessError, KeylessErrorType, SigningScheme } from "../types"; import { AccountAddress } from "../core/accountAddress"; import { AnyPublicKey, @@ -12,9 +13,10 @@ import { EphemeralCertificate, ZeroKnowledgeSig, ZkProof, + getPatchedJWKs, + MoveJWK, } from "../core/crypto"; -import { Account } from "./Account"; import { EphemeralKeyPair } from "./EphemeralKeyPair"; import { Hex } from "../core/hex"; import { AccountAuthenticatorSingleKey } from "../transactions/authenticator/account"; @@ -23,12 +25,25 @@ import { deriveTransactionType, generateSigningMessage } from "../transactions/t import { AnyRawTransaction, AnyRawTransactionInstance } from "../transactions/types"; import { base64UrlDecode } from "../utils/helpers"; import { FederatedKeylessPublicKey } from "../core/crypto/federatedKeyless"; +import { Account } from "./Account"; +import { AptosConfig } from "../api/aptosConfig"; + +/** + * An interface which defines if an Account utilizes Keyless signing. + */ +export interface KeylessSigner extends Account { + checkKeylessAccountValidity(aptosConfig: AptosConfig): Promise; +} + +export function isKeylessSigner(obj: any): obj is KeylessSigner { + return obj !== null && obj !== undefined && typeof obj.checkKeylessAccountValidity === "function"; +} /** * Account implementation for the Keyless authentication scheme. This abstract class is used for standard Keyless Accounts * and Federated Keyless Accounts. */ -export abstract class AbstractKeylessAccount extends Serializable implements Account { +export abstract class AbstractKeylessAccount extends Serializable implements KeylessSigner { static readonly PEPPER_LENGTH: number = 31; /** @@ -231,6 +246,32 @@ export abstract class AbstractKeylessAccount extends Serializable implements Acc } } + /** + * Validates that the Keyless Account can be used to sign transactions. + * @return + */ + async checkKeylessAccountValidity(aptosConfig: AptosConfig): Promise { + if (this.isExpired()) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.EPHEMERAL_KEY_PAIR_EXPIRED, + }); + } + await this.waitForProofFetch(); + if (this.proof === undefined) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.ASYNC_PROOF_FETCH_FAILED, + }); + } + const header = jwtDecode(this.jwt, { header: true }); + if (header.kid === undefined) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.JWT_PARSING_ERROR, + details: "checkKeylessAccountValidity failed. JWT is missing 'kid' in header. This should never happen.", + }); + } + await AbstractKeylessAccount.fetchJWK({ aptosConfig, publicKey: this.publicKey, kid: header.kid }); + } + /** * Sign the given message using Keyless. * @param message in HexInput format @@ -239,10 +280,15 @@ export abstract class AbstractKeylessAccount extends Serializable implements Acc sign(message: HexInput): KeylessSignature { const { expiryDateSecs } = this.ephemeralKeyPair; if (this.isExpired()) { - throw new Error("EphemeralKeyPair is expired"); + throw KeylessError.fromErrorType({ + type: KeylessErrorType.EPHEMERAL_KEY_PAIR_EXPIRED, + }); } if (this.proof === undefined) { - throw new Error("Proof not found - make sure to call `await account.waitForProofFetch()` before signing."); + throw KeylessError.fromErrorType({ + type: KeylessErrorType.PROOF_NOT_FOUND, + details: "Proof not found - make sure to call `await account.checkKeylessAccountValidity()` before signing.", + }); } const ephemeralPublicKey = this.ephemeralKeyPair.getPublicKey(); const ephemeralSignature = this.ephemeralKeyPair.sign(message); @@ -264,7 +310,10 @@ export abstract class AbstractKeylessAccount extends Serializable implements Acc */ signTransaction(transaction: AnyRawTransaction): KeylessSignature { if (this.proof === undefined) { - throw new Error("Proof not found - make sure to call `await account.waitForProofFetch()` before signing."); + throw KeylessError.fromErrorType({ + type: KeylessErrorType.PROOF_NOT_FOUND, + details: "Proof not found - make sure to call `await account.checkKeylessAccountValidity()` before signing.", + }); } const raw = deriveTransactionType(transaction); const txnAndProof = new TransactionAndProof(raw, this.proof.proof); @@ -293,6 +342,56 @@ export abstract class AbstractKeylessAccount extends Serializable implements Acc } return true; } + + /** + * Fetches the JWK from the issuer's well-known JWKS endpoint. + * + * @param args.publicKey The keyless public key to query + * @param args.kid The kid of the JWK to fetch + * @returns A JWK matching the `kid` in the JWT header. + * @throws {KeylessError} If the JWK cannot be fetched + */ + static async fetchJWK(args: { + aptosConfig: AptosConfig; + publicKey: KeylessPublicKey | FederatedKeylessPublicKey; + kid: string; + }): Promise { + const { aptosConfig, publicKey, kid } = args; + const keylessPubKey = publicKey instanceof KeylessPublicKey ? publicKey : publicKey.keylessPublicKey; + const { iss } = keylessPubKey; + + let patchedJWKs: Map; + try { + patchedJWKs = await getPatchedJWKs({ aptosConfig }); + } catch (error) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.JWK_FETCH_FAILED, + details: `Failed to fetch patched JWKs: ${error}`, + }); + } + + // Find the corresponding JWK set by `iss` + const jwksForIssuer = patchedJWKs.get(iss); + + if (jwksForIssuer === undefined) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.INVALID_JWT_ISS_NOT_RECOGNIZED, + details: `JWKs for issuer ${iss} not found.`, + }); + } + + // Find the corresponding JWK by `kid` + const jwk = jwksForIssuer.find((key) => key.kid === kid); + + if (jwk === undefined) { + throw KeylessError.fromErrorType({ + type: KeylessErrorType.INVALID_JWT_JWK_NOT_FOUND, + details: `JWK with kid ${kid} for issuer ${iss} not found.`, + }); + } + + return jwk; + } } /** diff --git a/src/account/MultiKeyAccount.ts b/src/account/MultiKeyAccount.ts index 6cb37d8ac..cded1c52a 100644 --- a/src/account/MultiKeyAccount.ts +++ b/src/account/MultiKeyAccount.ts @@ -7,7 +7,8 @@ import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; import { HexInput, SigningScheme } from "../types"; import { AccountAuthenticatorMultiKey } from "../transactions/authenticator/account"; import { AnyRawTransaction } from "../transactions/types"; -import { AbstractKeylessAccount } from "./AbstractKeylessAccount"; +import { AbstractKeylessAccount, KeylessSigner } from "./AbstractKeylessAccount"; +import { AptosConfig } from "../api/aptosConfig"; /** * Arguments required to verify a multi-key signature against a given message. @@ -28,7 +29,7 @@ export interface VerifyMultiKeySignatureArgs { * * Note: Generating a signer instance does not create the account on-chain. */ -export class MultiKeyAccount implements Account { +export class MultiKeyAccount implements Account, KeylessSigner { /** * Public key associated with the account */ @@ -154,9 +155,21 @@ export class MultiKeyAccount implements Account { } /** - * Sign the given data using the MultiKeyAccount's signers. - * @param data - The data to be signed in HexInput format. - * @returns MultiKeySignature - The resulting multi-key signature. + * Validates that the Keyless Account can be used to sign transactions. + * @return + */ + async checkKeylessAccountValidity(aptosConfig: AptosConfig): Promise { + const keylessSigners = this.signers.filter( + (signer) => signer instanceof AbstractKeylessAccount, + ) as AbstractKeylessAccount[]; + const promises = keylessSigners.map((signer) => signer.checkKeylessAccountValidity(aptosConfig)); + await Promise.all(promises); + } + + /** + * Sign the given message using the MultiKeyAccount's signers + * @param message in HexInput format + * @returns MultiKeySignature */ sign(data: HexInput): MultiKeySignature { const signatures = []; diff --git a/src/client/core.ts b/src/client/core.ts index 76302294a..e258038fe 100644 --- a/src/client/core.ts +++ b/src/client/core.ts @@ -2,9 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 import { AptosConfig } from "../api/aptosConfig"; -import { AptosApiError, AptosResponse } from "./types"; import { VERSION } from "../version"; -import { AnyNumber, AptosRequest, Client, ClientRequest, ClientResponse, MimeType } from "../types"; +import { + AnyNumber, + AptosApiError, + AptosRequest, + AptosResponse, + Client, + ClientRequest, + ClientResponse, + MimeType, +} from "../types"; import { AptosApiType } from "../utils"; /** diff --git a/src/client/get.ts b/src/client/get.ts index e353ce431..e3fd9c007 100644 --- a/src/client/get.ts +++ b/src/client/get.ts @@ -1,7 +1,6 @@ import { AptosConfig } from "../api/aptosConfig"; import { aptosRequest } from "./core"; -import { AptosResponse } from "./types"; -import { AnyNumber, ClientConfig, MimeType } from "../types"; +import { AptosResponse, AnyNumber, ClientConfig, MimeType } from "../types"; import { AptosApiType } from "../utils/const"; /** diff --git a/src/client/index.ts b/src/client/index.ts index 5a78810ea..a73eed955 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -4,4 +4,3 @@ export * from "./core"; export * from "./get"; export * from "./post"; -export * from "./types"; diff --git a/src/client/post.ts b/src/client/post.ts index 9a12d714e..e08f8da13 100644 --- a/src/client/post.ts +++ b/src/client/post.ts @@ -3,8 +3,7 @@ import { AptosConfig } from "../api/aptosConfig"; import { aptosRequest } from "./core"; -import { AptosResponse } from "./types"; -import { AnyNumber, ClientConfig, MimeType } from "../types"; +import { AptosResponse, AnyNumber, ClientConfig, MimeType } from "../types"; import { AptosApiType } from "../utils/const"; /** diff --git a/src/client/types.ts b/src/client/types.ts deleted file mode 100644 index ee397c94c..000000000 --- a/src/client/types.ts +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -import { AptosRequest } from "../types"; -import { AptosApiType } from "../utils/const.js"; - -/** - * The API response type - * - * @param status - the response status. i.e. 200 - * @param statusText - the response message - * @param data the response data - * @param url the url the request was made to - * @param headers the response headers - * @param config (optional) - the request object - * @param request (optional) - the request object - */ -export interface AptosResponse { - status: number; - statusText: string; - data: Res; - url: string; - headers: any; - config?: any; - request?: Req; -} - -/** - * Options for handling errors in the Aptos API. - */ -type AptosApiErrorOpts = { - apiType: AptosApiType; - aptosRequest: AptosRequest; - aptosResponse: AptosResponse; -}; - -/** - * Represents an error returned from the Aptos API. - * This class encapsulates the details of the error, including the request URL, response status, and additional data. - * - * @param name - The name of the error, which is always "AptosApiError". - * @param url - The URL to which the request was made. - * @param status - The HTTP response status code (e.g., 400). - * @param statusText - The message associated with the response status. - * @param data - The response data returned from the API. - * @param request - The original AptosRequest that triggered the error. - */ -export class AptosApiError extends Error { - readonly url: string; - - readonly status: number; - - readonly statusText: string; - - readonly data: any; - - readonly request: AptosRequest; - - /** - * Constructs an instance of AptosApiError with relevant error details. - * - * @param opts - The options for creating the AptosApiError. - * @param opts.apiType - The type of API that generated the error. - * @param opts.aptosRequest - The request object that caused the error. - * @param opts.aptosResponse - The response object containing error details. - * - * @internal This constructor is for SDK internal use - do not instantiate outside the SDK codebase. - */ - constructor({ apiType, aptosRequest, aptosResponse }: AptosApiErrorOpts) { - super(deriveErrorMessage({ apiType, aptosRequest, aptosResponse })); - - this.name = "AptosApiError"; - this.url = aptosResponse.url; - this.status = aptosResponse.status; - this.statusText = aptosResponse.statusText; - this.data = aptosResponse.data; - this.request = aptosRequest; - } -} - -/** - * Derives an error message from the Aptos API response, providing context for debugging. - * This function helps in understanding the nature of the error encountered during an API request. - * - * @param {AptosApiErrorOpts} opts - The options for deriving the error message. - * @param {AptosApiType} opts.apiType - The type of API being called. - * @param {AptosRequest} opts.aptosRequest - The original request made to the Aptos API. - * @param {AptosResponse} opts.aptosResponse - The response received from the Aptos API. - */ -function deriveErrorMessage({ apiType, aptosRequest, aptosResponse }: AptosApiErrorOpts): string { - // extract the W3C trace_id from the response headers if it exists. Some services set this in the response, and it's useful for debugging. - // See https://www.w3.org/TR/trace-context/#relationship-between-the-headers . - const traceId = aptosResponse.headers?.traceparent?.split("-")[1]; - const traceIdString = traceId ? `(trace_id:${traceId}) ` : ""; - - const errorPrelude: string = `Request to [${apiType}]: ${aptosRequest.method} ${ - aptosResponse.url ?? aptosRequest.url - } ${traceIdString}failed with`; - - // handle graphql responses from indexer api and extract the error message of the first error - if (apiType === AptosApiType.INDEXER && aptosResponse.data?.errors?.[0]?.message != null) { - return `${errorPrelude}: ${aptosResponse.data.errors[0].message}`; - } - - // Received well-known structured error response body - simply serialize and return it. - // We don't need http status codes etc. in this case. - if (aptosResponse.data?.message != null && aptosResponse.data?.error_code != null) { - return `${errorPrelude}: ${JSON.stringify(aptosResponse.data)}`; - } - - // This is the generic/catch-all case. We received some response from the API, but it doesn't appear to be a well-known structure. - // We print http status codes and the response body (after some trimming), - // in the hope that this gives enough context what went wrong without printing overly huge messages. - return `${errorPrelude} status: ${aptosResponse.statusText}(code:${ - aptosResponse.status - }) and response body: ${serializeAnyPayloadForErrorMessage(aptosResponse.data)}`; -} - -const SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH = 400; - -/** - * This function accepts a payload of any type (probably an object) and serializes it to a string - * Since we don't know the type or size of the payload, and we don't want to add a huge object in full to the error message - * we limit the to the first 200 and last 200 characters of the serialized payload and put a "..." in the middle. - * @param payload - The payload to serialize, which can be of any type. - * - * @returns A string representation of the serialized payload, potentially truncated. - */ -function serializeAnyPayloadForErrorMessage(payload: any): string { - const serializedPayload = JSON.stringify(payload); - if (serializedPayload.length <= SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH) { - return serializedPayload; - } - return `truncated(original_size:${serializedPayload.length}): ${serializedPayload.slice( - 0, - SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH / 2, - )}...${serializedPayload.slice(-SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH / 2)}`; -} diff --git a/src/core/crypto/keyless.ts b/src/core/crypto/keyless.ts index e149a2fd1..11610c611 100644 --- a/src/core/crypto/keyless.ts +++ b/src/core/crypto/keyless.ts @@ -1,11 +1,12 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 +// eslint-disable-next-line max-classes-per-file import { JwtPayload, jwtDecode } from "jwt-decode"; import { AccountPublicKey, PublicKey } from "./publicKey"; import { Signature } from "./signature"; import { Deserializer, Serializable, Serializer } from "../../bcs"; -import { Hex } from "../hex"; +import { Hex, hexToAsciiString } from "../hex"; import { HexInput, EphemeralCertificateVariant, @@ -20,11 +21,16 @@ import { bigIntToBytesLE, bytesToBigIntLE, hashStrToField, poseidonHash } from " import { AuthenticationKey } from "../authenticationKey"; import { Proof } from "./proof"; import { Ed25519PublicKey, Ed25519Signature } from "./ed25519"; -import { Groth16VerificationKeyResponse, KeylessConfigurationResponse } from "../../types/keyless"; +import { + Groth16VerificationKeyResponse, + KeylessConfigurationResponse, + MoveAnyStruct, + PatchedJWKsResponse, +} from "../../types/keyless"; import { AptosConfig } from "../../api/aptosConfig"; import { getAptosFullNode } from "../../client"; import { memoizeAsync } from "../../utils/memoize"; -import { AccountAddress } from "../accountAddress"; +import { AccountAddress, AccountAddressInput } from "../accountAddress"; export const EPK_HORIZON_SECS = 10000000; export const MAX_AUD_VAL_BYTES = 120; @@ -282,6 +288,15 @@ export class KeylessSignature extends Signature { this.ephemeralSignature = ephemeralSignature; } + /** + * Get the kid of the JWT used to derive the Keyless Account used to sign. + * + * @returns the kid as a string + */ + getJwkKid(): string { + return parseJwtHeader(this.jwtHeader).kid; + } + serialize(serializer: Serializer): void { this.ephemeralCertificate.serialize(serializer); serializer.serializeStr(this.jwtHeader); @@ -764,3 +779,98 @@ async function getGroth16VerificationKeyResource(args: { return data.data; } + +export async function getPatchedJWKs(args: { + aptosConfig: AptosConfig; + jwkAddr?: AccountAddressInput; + options?: LedgerVersionArg; +}): Promise> { + const { aptosConfig, jwkAddr = "0x1", options } = args; + const resourceType = `${jwkAddr.toString()}::jwks::PatchedJWKs`; + const { data } = await getAptosFullNode<{}, MoveResource>({ + aptosConfig, + originMethod: "getPatchedJWKs", + path: `accounts/${AccountAddress.from("0x1").toString()}/resource/${resourceType}`, + params: { ledger_version: options?.ledgerVersion }, + }); + + // Create a map of issuer to JWK arrays + const jwkMap = new Map(); + for (const entry of data.data.jwks.entries) { + const jwks: MoveJWK[] = []; + for (const jwkStruct of entry.jwks) { + const { data: jwkData } = jwkStruct.variant; + const deserializer = new Deserializer(Hex.fromHexInput(jwkData).toUint8Array()); + const jwk = MoveJWK.deserialize(deserializer); + jwks.push(jwk); + } + jwkMap.set(hexToAsciiString(entry.issuer), jwks); + } + + return jwkMap; +} + +export class MoveJWK extends Serializable { + public kid: string; + + public kty: string; + + public alg: string; + + public e: string; + + public n: string; + + constructor(args: { kid: string; kty: string; alg: string; e: string; n: string }) { + super(); + const { kid, kty, alg, e, n } = args; + this.kid = kid; + this.kty = kty; + this.alg = alg; + this.e = e; + this.n = n; + } + + serialize(serializer: Serializer): void { + serializer.serializeStr(this.kid); + serializer.serializeStr(this.kty); + serializer.serializeStr(this.alg); + serializer.serializeStr(this.e); + serializer.serializeStr(this.n); + } + + static fromMoveStruct(struct: MoveAnyStruct): MoveJWK { + const { data } = struct.variant; + const deserializer = new Deserializer(Hex.fromHexInput(data).toUint8Array()); + return MoveJWK.deserialize(deserializer); + } + + static deserialize(deserializer: Deserializer): MoveJWK { + const kid = deserializer.deserializeStr(); + const kty = deserializer.deserializeStr(); + const alg = deserializer.deserializeStr(); + const n = deserializer.deserializeStr(); + const e = deserializer.deserializeStr(); + return new MoveJWK({ kid, kty, alg, n, e }); + } +} + +interface JwtHeader { + kid: string; // Key ID +} +/** + * Safely parses the JWT header. + * @param jwtHeader The JWT header string + * @returns Parsed JWT header as an object. + */ +export function parseJwtHeader(jwtHeader: string): JwtHeader { + try { + const header = JSON.parse(jwtHeader); + if (header.kid === undefined) { + throw new Error("JWT header missing kid"); + } + return header; + } catch (error) { + throw new Error("Failed to parse JWT header."); + } +} diff --git a/src/core/hex.ts b/src/core/hex.ts index 1fef0881e..741630a82 100644 --- a/src/core/hex.ts +++ b/src/core/hex.ts @@ -172,3 +172,5 @@ export class Hex { return this.data.every((value, index) => value === other.data[index]); } } + +export const hexToAsciiString = (hex: string) => new TextDecoder().decode(Hex.fromHexInput(hex).toUint8Array()); diff --git a/src/internal/account.ts b/src/internal/account.ts index edf1c8e1a..bba39286f 100644 --- a/src/internal/account.ts +++ b/src/internal/account.ts @@ -7,15 +7,11 @@ * other namespaces and processes can access these methods without depending on the entire * account namespace and without having a dependency cycle error. */ - import { AptosConfig } from "../api/aptosConfig"; -import { AptosApiError, getAptosFullNode, paginateWithCursor } from "../client"; -import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; -import { Account } from "../account"; -import { AnyPublicKey, Ed25519PublicKey, PrivateKey } from "../core/crypto"; -import { queryIndexer } from "./general"; +import { getAptosFullNode, paginateWithCursor } from "../client"; import { AccountData, + AptosApiError, GetAccountCoinsDataResponse, GetAccountCollectionsWithOwnedTokenResponse, GetAccountOwnedTokensFromCollectionResponse, @@ -31,6 +27,10 @@ import { TransactionResponse, WhereArg, } from "../types"; +import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; +import { Account } from "../account"; +import { AnyPublicKey, Ed25519PublicKey, PrivateKey } from "../core/crypto"; +import { queryIndexer } from "./general"; import { GetAccountCoinsCountQuery, GetAccountCoinsDataQuery, diff --git a/src/internal/keyless.ts b/src/internal/keyless.ts index c4e16f664..4f70aaf1c 100644 --- a/src/internal/keyless.ts +++ b/src/internal/keyless.ts @@ -16,11 +16,12 @@ import { Groth16Zkp, Hex, KeylessPublicKey, + MoveJWK, ZeroKnowledgeSig, ZkProof, getKeylessConfig, } from "../core"; -import { HexInput, ZkpVariant } from "../types"; +import { HexInput, KeylessError, KeylessErrorType, ZkpVariant } from "../types"; import { Account, EphemeralKeyPair, KeylessAccount, ProofFetchCallback } from "../account"; import { PepperFetchRequest, PepperFetchResponse, ProverRequest, ProverResponse } from "../types/keyless"; import { lookupOriginalAccountAddress } from "./account"; @@ -201,24 +202,8 @@ export async function deriveKeylessAccount(args: { return KeylessAccount.create({ ...args, address, proof, pepper, proofFetchCallback }); } -/** - * A JSON Web Keyset (JWK) - * - * Used to verify JSON Web Tokens (JWTs). - */ -interface JWK { - kty: string; // Key type - kid: string; // Key ID - alg: string; // Algorithm used with the key - n: string; // Modulus (for RSA keys) - e: string; // Exponent (for RSA keys) -} - -/** - * A collection of JSON Web Key Set (JWKS). - */ -interface JWKS { - keys: JWK[]; +export interface JWKS { + keys: MoveJWK[]; } export async function updateFederatedKeylessJwkSetTransaction(args: { @@ -230,10 +215,27 @@ export async function updateFederatedKeylessJwkSetTransaction(args: { }): Promise { const { aptosConfig, sender, iss, options } = args; const jwksUrl = args.jwksUrl ?? (iss.endsWith("/") ? `${iss}.well-known/jwks.json` : `${iss}/.well-known/jwks.json`); - const response = await fetch(jwksUrl); - if (!response.ok) { - throw new Error(`Failed to fetch JWKS: ${response.status} ${response.statusText}`); + + let response: Response; + + try { + response = await fetch(jwksUrl); + if (!response.ok) { + throw new Error(`${response.status} ${response.statusText}`); + } + } catch (error) { + let errorMessage: string; + if (error instanceof Error) { + errorMessage = `${error.message}`; + } else { + errorMessage = `error unknown - ${error}`; + } + throw KeylessError.fromErrorType({ + type: KeylessErrorType.JWK_FETCH_FAILED_FEDERATED, + details: `Failed to fetch JWKS at ${jwksUrl}: ${errorMessage}`, + }); } + const jwks: JWKS = await response.json(); return generateTransaction({ aptosConfig, diff --git a/src/internal/transaction.ts b/src/internal/transaction.ts index 171e41d37..84459893f 100644 --- a/src/internal/transaction.ts +++ b/src/internal/transaction.ts @@ -9,8 +9,9 @@ */ import { AptosConfig } from "../api/aptosConfig"; -import { AptosApiError, getAptosFullNode, paginateWithCursor } from "../client"; +import { getAptosFullNode, paginateWithCursor } from "../client"; import { + AptosApiError, TransactionResponseType, type AnyNumber, type GasEstimation, diff --git a/src/internal/transactionSubmission.ts b/src/internal/transactionSubmission.ts index f0ef49f84..28fb335bb 100644 --- a/src/internal/transactionSubmission.ts +++ b/src/internal/transactionSubmission.ts @@ -6,11 +6,11 @@ */ import { AptosConfig } from "../api/aptosConfig"; -import { MoveVector, U8 } from "../bcs"; +import { Deserializer, MoveVector, U8 } from "../bcs"; import { postAptosFullNode } from "../client"; -import { Account, AbstractKeylessAccount, MultiKeyAccount } from "../account"; +import { Account, AbstractKeylessAccount, MultiKeyAccount, isKeylessSigner } from "../account"; import { AccountAddress, AccountAddressInput } from "../core/accountAddress"; -import { PrivateKey } from "../core/crypto"; +import { FederatedKeylessPublicKey, KeylessPublicKey, KeylessSignature, PrivateKey } from "../core/crypto"; import { AccountAuthenticator } from "../transactions/authenticator/account"; import { RotationProofChallenge } from "../transactions/instances/rotationProofChallenge"; import { @@ -33,7 +33,7 @@ import { } from "../transactions/types"; import { getInfo } from "./account"; import { UserTransactionResponse, PendingTransactionResponse, MimeType, HexInput, TransactionResponse } from "../types"; -import { TypeTagU8, TypeTagVector, generateSigningMessageForTransaction } from "../transactions"; +import { SignedTransaction, TypeTagU8, TypeTagVector, generateSigningMessageForTransaction } from "../transactions"; import { SimpleTransaction } from "../transactions/instances/simpleTransaction"; import { MultiAgentTransaction } from "../transactions/instances/multiAgentTransaction"; @@ -312,15 +312,33 @@ export async function submitTransaction( ): Promise { const { aptosConfig } = args; const signedTransaction = generateSignedTransaction({ ...args }); - const { data } = await postAptosFullNode({ - aptosConfig, - body: signedTransaction, - path: "transactions", - originMethod: "submitTransaction", - contentType: MimeType.BCS_SIGNED_TRANSACTION, - }); - return data; + try { + const { data } = await postAptosFullNode({ + aptosConfig, + body: signedTransaction, + path: "transactions", + originMethod: "submitTransaction", + contentType: MimeType.BCS_SIGNED_TRANSACTION, + }); + return data; + } catch (e) { + const signedTxn = SignedTransaction.deserialize(new Deserializer(signedTransaction)); + if ( + signedTxn.authenticator.isSingleSender() && + signedTxn.authenticator.sender.isSingleKey() && + (signedTxn.authenticator.sender.public_key.publicKey instanceof KeylessPublicKey || + signedTxn.authenticator.sender.public_key.publicKey instanceof FederatedKeylessPublicKey) + ) { + await AbstractKeylessAccount.fetchJWK({ + aptosConfig, + publicKey: signedTxn.authenticator.sender.public_key.publicKey, + kid: (signedTxn.authenticator.sender.signature.signature as KeylessSignature).getJwkKid(), + }); + } + throw e; + } } + export type FeePayerOrFeePayerAuthenticatorOrNeither = | { feePayer: Account; feePayerAuthenticator?: never } | { feePayer?: never; feePayerAuthenticator: AccountAuthenticator } @@ -339,8 +357,8 @@ export async function signAndSubmitTransaction( if (signer instanceof AbstractKeylessAccount || signer instanceof MultiKeyAccount) { await signer.waitForProofFetch(); } - if (feePayer instanceof AbstractKeylessAccount || feePayer instanceof MultiKeyAccount) { - await feePayer.waitForProofFetch(); + if (isKeylessSigner(feePayer)) { + await feePayer.checkKeylessAccountValidity(aptosConfig); } const feePayerAuthenticator = args.feePayerAuthenticator || (feePayer && signAsFeePayer({ signer: feePayer, transaction })); @@ -362,8 +380,8 @@ export async function signAndSubmitAsFeePayer(args: { }): Promise { const { aptosConfig, senderAuthenticator, feePayer, transaction } = args; - if (feePayer instanceof AbstractKeylessAccount || feePayer instanceof MultiKeyAccount) { - await feePayer.waitForProofFetch(); + if (isKeylessSigner(feePayer)) { + await feePayer.checkKeylessAccountValidity(aptosConfig); } const feePayerAuthenticator = signAsFeePayer({ signer: feePayer, transaction }); diff --git a/src/transactions/authenticator/transaction.ts b/src/transactions/authenticator/transaction.ts index a5c7d4d73..765a01fca 100644 --- a/src/transactions/authenticator/transaction.ts +++ b/src/transactions/authenticator/transaction.ts @@ -42,6 +42,26 @@ export abstract class TransactionAuthenticator extends Serializable { throw new Error(`Unknown variant index for TransactionAuthenticator: ${index}`); } } + + isEd25519(): this is TransactionAuthenticatorEd25519 { + return this instanceof TransactionAuthenticatorEd25519; + } + + isMultiEd25519(): this is TransactionAuthenticatorMultiEd25519 { + return this instanceof TransactionAuthenticatorMultiEd25519; + } + + isMultiAgent(): this is TransactionAuthenticatorMultiAgent { + return this instanceof TransactionAuthenticatorMultiAgent; + } + + isFeePayer(): this is TransactionAuthenticatorFeePayer { + return this instanceof TransactionAuthenticatorFeePayer; + } + + isSingleSender(): this is TransactionAuthenticatorSingleSender { + return this instanceof TransactionAuthenticatorSingleSender; + } } /** diff --git a/src/types/error.ts b/src/types/error.ts new file mode 100644 index 000000000..7d2222a2e --- /dev/null +++ b/src/types/error.ts @@ -0,0 +1,381 @@ +import { AptosApiType } from "../utils/const"; +import { AptosRequest } from "./types"; + +export enum KeylessErrorCategory { + API_ERROR, + EXTERNAL_API_ERROR, + SESSION_EXPIRED, + INVALID_STATE, + UNKNOWN, +} + +export enum KeylessErrorResolutionTip { + REAUTHENTICATE = "Re-authentiate to continue using your keyless account", + // eslint-disable-next-line max-len + REAUTHENTICATE_UNSURE = "Try re-authentiating. If the error persists join the telegram group at https://t.me/+h5CN-W35yUFiYzkx for further support", + UPDATE_REQUEST_PARAMS = "Update the invalid request parameters and reauthenticate.", + // eslint-disable-next-line max-len + RATE_LIMIT_EXCEEDED = "Cache the keyless account and reuse it to avoid making too many requests. Keyless accounts are valid until either the EphemeralKeyPair expires, when the JWK is rotated, or when the proof verifying key is changed, whichever comes soonest.", + // eslint-disable-next-line max-len + SERVER_ERROR = "Try again later. See aptosApiError error for more context. For additional support join the telegram group at https://t.me/+h5CN-W35yUFiYzkx", + // eslint-disable-next-line max-len + CALL_PRECHECK = "Call `await account.checkKeylessAccountValidity()` to wait for asyncronous changes and check for account validity before signing or serializing.", + REINSTANTIATE = "Try instantiating the account again. Avoid manipulating the account object directly", + JOIN_SUPPORT_GROUP = "For support join the telegram group at https://t.me/+h5CN-W35yUFiYzkx", + UNKNOWN = "Error unknown. For support join the telegram group at https://t.me/+h5CN-W35yUFiYzkx", +} + +export enum KeylessErrorType { + EPHEMERAL_KEY_PAIR_EXPIRED, + + PROOF_NOT_FOUND, + + ASYNC_PROOF_FETCH_FAILED, + + INVALID_PROOF_VERIFICATION_FAILED, + + INVALID_PROOF_VERIFICATION_KEY_NOT_FOUND, + + INVALID_JWT_SIG, + + INVALID_JWT_JWK_NOT_FOUND, + + INVALID_JWT_ISS_NOT_RECOGNIZED, + + INVALID_JWT_FEDERATED_ISS_NOT_SUPPORTED, + + INVALID_TW_SIG_VERIFICATION_FAILED, + + INVALID_TW_SIG_PUBLIC_KEY_NOT_FOUND, + + INVALID_EXPIRY_HORIZON, + + JWT_PARSING_ERROR, + + JWK_FETCH_FAILED, + + JWK_FETCH_FAILED_FEDERATED, + + RATE_LIMIT_EXCEEDED, + + PEPPER_SERVICE_INTERNAL_ERROR, + + PEPPER_SERVICE_BAD_REQUEST, + + PEPPER_SERVICE_OTHER, + + PROVER_SERVICE_INTERNAL_ERROR, + + PROVER_SERVICE_BAD_REQUEST, + + PROVER_SERVICE_OTHER, + + UNKNOWN, +} + +const KeylessErrors: { [key in KeylessErrorType]: [string, KeylessErrorCategory, KeylessErrorResolutionTip] } = { + [KeylessErrorType.EPHEMERAL_KEY_PAIR_EXPIRED]: [ + "The ephemeral keypair has expired.", + KeylessErrorCategory.SESSION_EXPIRED, + KeylessErrorResolutionTip.REAUTHENTICATE, + ], + [KeylessErrorType.PROOF_NOT_FOUND]: [ + "The required proof could not be found.", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.CALL_PRECHECK, + ], + [KeylessErrorType.ASYNC_PROOF_FETCH_FAILED]: [ + "The required proof failed to fetch.", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.REAUTHENTICATE_UNSURE, + ], + [KeylessErrorType.INVALID_PROOF_VERIFICATION_FAILED]: [ + "The provided proof is invalid.", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.REAUTHENTICATE_UNSURE, + ], + [KeylessErrorType.INVALID_PROOF_VERIFICATION_KEY_NOT_FOUND]: [ + "The verification key used to authenticate was updated.", + KeylessErrorCategory.SESSION_EXPIRED, + KeylessErrorResolutionTip.REAUTHENTICATE, + ], + [KeylessErrorType.INVALID_JWT_SIG]: [ + "The JWK was found, but JWT failed verification", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.REAUTHENTICATE_UNSURE, + ], + [KeylessErrorType.INVALID_JWT_JWK_NOT_FOUND]: [ + "The JWK required to verify the JWT could not be found. The JWK may have been rotated out.", + KeylessErrorCategory.SESSION_EXPIRED, + KeylessErrorResolutionTip.REAUTHENTICATE, + ], + [KeylessErrorType.INVALID_JWT_ISS_NOT_RECOGNIZED]: [ + "The JWT issuer is not recognized.", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.UPDATE_REQUEST_PARAMS, + ], + [KeylessErrorType.INVALID_JWT_FEDERATED_ISS_NOT_SUPPORTED]: [ + "The JWT issuer is not supported by the Federated Keyless ", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.REAUTHENTICATE_UNSURE, + ], + [KeylessErrorType.INVALID_TW_SIG_VERIFICATION_FAILED]: [ + "The training wheels signature is invalid.", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.REAUTHENTICATE_UNSURE, + ], + [KeylessErrorType.INVALID_TW_SIG_PUBLIC_KEY_NOT_FOUND]: [ + "The public key used to verify the training wheels signature was not found.", + KeylessErrorCategory.SESSION_EXPIRED, + KeylessErrorResolutionTip.REAUTHENTICATE, + ], + [KeylessErrorType.INVALID_EXPIRY_HORIZON]: [ + "The expiry horizon is invalid.", + KeylessErrorCategory.SESSION_EXPIRED, + KeylessErrorResolutionTip.REAUTHENTICATE, + ], + [KeylessErrorType.JWK_FETCH_FAILED]: [ + "Failed to fetch JWKS.", + KeylessErrorCategory.EXTERNAL_API_ERROR, + KeylessErrorResolutionTip.JOIN_SUPPORT_GROUP, + ], + [KeylessErrorType.JWK_FETCH_FAILED_FEDERATED]: [ + "Failed to fetch JWKS for Federated Keyless provider.", + KeylessErrorCategory.EXTERNAL_API_ERROR, + KeylessErrorResolutionTip.JOIN_SUPPORT_GROUP, + ], + [KeylessErrorType.RATE_LIMIT_EXCEEDED]: [ + "Rate limit exceeded. Too many requests in a short period.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.RATE_LIMIT_EXCEEDED, + ], + [KeylessErrorType.PEPPER_SERVICE_INTERNAL_ERROR]: [ + "Internal error from Pepper service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.SERVER_ERROR, + ], + [KeylessErrorType.PEPPER_SERVICE_BAD_REQUEST]: [ + "Bad request sent to Pepper service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.UPDATE_REQUEST_PARAMS, + ], + [KeylessErrorType.PEPPER_SERVICE_OTHER]: [ + "Unknown error from Pepper service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.SERVER_ERROR, + ], + [KeylessErrorType.PROVER_SERVICE_INTERNAL_ERROR]: [ + "Internal error from Prover service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.SERVER_ERROR, + ], + [KeylessErrorType.PROVER_SERVICE_BAD_REQUEST]: [ + "Bad request sent to Prover service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.UPDATE_REQUEST_PARAMS, + ], + [KeylessErrorType.PROVER_SERVICE_OTHER]: [ + "Unknown error from Prover service.", + KeylessErrorCategory.API_ERROR, + KeylessErrorResolutionTip.SERVER_ERROR, + ], + [KeylessErrorType.JWT_PARSING_ERROR]: [ + "Error when parsing JWT. This should never happen. Join https://t.me/+h5CN-W35yUFiYzkx for support", + KeylessErrorCategory.INVALID_STATE, + KeylessErrorResolutionTip.REINSTANTIATE, + ], + [KeylessErrorType.UNKNOWN]: [ + "An unknown error has occurred.", + KeylessErrorCategory.UNKNOWN, + KeylessErrorResolutionTip.UNKNOWN, + ], +}; + +export class KeylessError extends Error { + readonly aptosApiError?: AptosApiError; + + readonly category: KeylessErrorCategory; + + readonly resolutionTip: KeylessErrorResolutionTip; + + readonly type: KeylessErrorType; + + /** @internal this constructor is for sdk internal use - do not instantiate outside of the SDK codebase */ + constructor(args: { + aptosApiError?: AptosApiError; + category: KeylessErrorCategory; + resolutionTip: KeylessErrorResolutionTip; + type: KeylessErrorType; + message?: string; + details?: string; + }) { + const { aptosApiError, category, resolutionTip, type, details } = args; + const error = KeylessErrors[type]; + super(error[0]); + this.name = "KeylessError"; + this.aptosApiError = aptosApiError; + this.category = category; + this.resolutionTip = resolutionTip; + this.type = type; + this.message = details ?? ""; + } + + /** + * Static constructor that creates a KeylessError instance using the KeylessErrors constant + * @param args.type The type of KeylessError + * @param args.aptosApiError optional AptosApiError supplied for api errors + * @param args.details optional details to include in the error message + * @returns A new KeylessError instance + */ + static fromErrorType(args: { + type: KeylessErrorType; + aptosApiError?: AptosApiError; + details?: string; + }): KeylessError { + const { aptosApiError, type, details } = args; + + const [message, category, resolutionTip] = KeylessErrors[type]; + return new KeylessError({ + message, + details, + aptosApiError, + category, + resolutionTip, + type, + }); + } +} + +/** + * The API response type + * + * @param status - the response status. i.e. 200 + * @param statusText - the response message + * @param data the response data + * @param url the url the request was made to + * @param headers the response headers + * @param config (optional) - the request object + * @param request (optional) - the request object + */ +export interface AptosResponse { + status: number; + statusText: string; + data: Res; + url: string; + headers: any; + config?: any; + request?: Req; +} + +/** + * Options for handling errors in the Aptos API. + */ +type AptosApiErrorOpts = { + apiType: AptosApiType; + aptosRequest: AptosRequest; + aptosResponse: AptosResponse; +}; + +/** + * Represents an error returned from the Aptos API. + * This class encapsulates the details of the error, including the request URL, response status, and additional data. + * + * @param name - The name of the error, which is always "AptosApiError". + * @param url - The URL to which the request was made. + * @param status - The HTTP response status code (e.g., 400). + * @param statusText - The message associated with the response status. + * @param data - The response data returned from the API. + * @param request - The original AptosRequest that triggered the error. + */ +export class AptosApiError extends Error { + readonly url: string; + + readonly status: number; + + readonly statusText: string; + + readonly data: any; + + readonly request: AptosRequest; + + /** + * Constructs an instance of AptosApiError with relevant error details. + * + * @param opts - The options for creating the AptosApiError. + * @param opts.apiType - The type of API that generated the error. + * @param opts.aptosRequest - The request object that caused the error. + * @param opts.aptosResponse - The response object containing error details. + * + * @internal This constructor is for SDK internal use - do not instantiate outside the SDK codebase. + */ + constructor({ apiType, aptosRequest, aptosResponse }: AptosApiErrorOpts) { + super(deriveErrorMessage({ apiType, aptosRequest, aptosResponse })); + + this.name = "AptosApiError"; + this.url = aptosResponse.url; + this.status = aptosResponse.status; + this.statusText = aptosResponse.statusText; + this.data = aptosResponse.data; + this.request = aptosRequest; + } +} + +/** + * Derives an error message from the Aptos API response, providing context for debugging. + * This function helps in understanding the nature of the error encountered during an API request. + * + * @param {AptosApiErrorOpts} opts - The options for deriving the error message. + * @param {AptosApiType} opts.apiType - The type of API being called. + * @param {AptosRequest} opts.aptosRequest - The original request made to the Aptos API. + * @param {AptosResponse} opts.aptosResponse - The response received from the Aptos API. + */ +function deriveErrorMessage({ apiType, aptosRequest, aptosResponse }: AptosApiErrorOpts): string { + // eslint-disable-next-line max-len + // extract the W3C trace_id from the response headers if it exists. Some services set this in the response, and it's useful for debugging. + // See https://www.w3.org/TR/trace-context/#relationship-between-the-headers . + const traceId = aptosResponse.headers?.traceparent?.split("-")[1]; + const traceIdString = traceId ? `(trace_id:${traceId}) ` : ""; + + const errorPrelude: string = `Request to [${apiType}]: ${aptosRequest.method} ${ + aptosResponse.url ?? aptosRequest.url + } ${traceIdString}failed with`; + + // handle graphql responses from indexer api and extract the error message of the first error + if (apiType === AptosApiType.INDEXER && aptosResponse.data?.errors?.[0]?.message != null) { + return `${errorPrelude}: ${aptosResponse.data.errors[0].message}`; + } + + // Received well-known structured error response body - simply serialize and return it. + // We don't need http status codes etc. in this case. + if (aptosResponse.data?.message != null && aptosResponse.data?.error_code != null) { + return `${errorPrelude}: ${JSON.stringify(aptosResponse.data)}`; + } + + // This is the generic/catch-all case. We received some response from the API, but it doesn't appear to be a well-known structure. + // We print http status codes and the response body (after some trimming), + // in the hope that this gives enough context what went wrong without printing overly huge messages. + return `${errorPrelude} status: ${aptosResponse.statusText}(code:${ + aptosResponse.status + }) and response body: ${serializeAnyPayloadForErrorMessage(aptosResponse.data)}`; +} + +const SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH = 400; + +/** + * This function accepts a payload of any type (probably an object) and serializes it to a string + * Since we don't know the type or size of the payload, and we don't want to add a huge object in full to the error message + * we limit the to the first 200 and last 200 characters of the serialized payload and put a "..." in the middle. + * @param payload - The payload to serialize, which can be of any type. + * + * @returns A string representation of the serialized payload, potentially truncated. + */ +function serializeAnyPayloadForErrorMessage(payload: any): string { + const serializedPayload = JSON.stringify(payload); + if (serializedPayload.length <= SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH) { + return serializedPayload; + } + return `truncated(original_size:${serializedPayload.length}): ${serializedPayload.slice( + 0, + SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH / 2, + )}...${serializedPayload.slice(-SERIALIZED_PAYLOAD_TRIM_TO_MAX_LENGTH / 2)}`; +} diff --git a/src/types/index.ts b/src/types/index.ts index ffa1f0f9d..a5bb252e5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,1493 +1,3 @@ -// Copyright © Aptos Foundation -// SPDX-License-Identifier: Apache-2.0 - -import { Network } from "../utils/apiEndpoints"; -import { OrderBy, TokenStandard } from "./indexer"; - export * from "./indexer"; - -/** - * Different MIME types used for data interchange in transactions and responses. - */ -export enum MimeType { - /** - * JSON representation, used for transaction submission and accept type JSON output - */ - JSON = "application/json", - /** - * BCS representation, used for accept type BCS output - */ - BCS = "application/x-bcs", - /** - * BCS representation, used for transaction submission in BCS input - */ - BCS_SIGNED_TRANSACTION = "application/x.aptos.signed_transaction+bcs", - BCS_VIEW_FUNCTION = "application/x.aptos.view_function+bcs", -} - -/** - * Hexadecimal data input for functions, supporting both string and Uint8Array formats. - */ -export type HexInput = string | Uint8Array; - -/** - * Variants of type tags used in the system, encompassing various data types and structures. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-core/types/src/language_storage.rs#L27} - */ -export enum TypeTagVariants { - Bool = 0, - U8 = 1, - U64 = 2, - U128 = 3, - Address = 4, - Signer = 5, - Vector = 6, - Struct = 7, - U16 = 8, - U32 = 9, - U256 = 10, - Reference = 254, // This is specifically a placeholder and does not represent a real type - Generic = 255, // This is specifically a placeholder and does not represent a real type -} - -/** - * Variants of script transaction arguments used in Rust, encompassing various data types for transaction processing. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-core/types/src/transaction_argument.rs#L11} - */ -export enum ScriptTransactionArgumentVariants { - U8 = 0, - U64 = 1, - U128 = 2, - Address = 3, - U8Vector = 4, - Bool = 5, - U16 = 6, - U32 = 7, - U256 = 8, - Serialized = 9, -} - -/** - * The payload for various transaction types in the system. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/mod.rs#L478} - */ -export enum TransactionPayloadVariants { - Script = 0, - EntryFunction = 2, - Multisig = 3, -} - -/** - * Variants of transactions used in the system. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/mod.rs#L440} - */ -export enum TransactionVariants { - MultiAgentTransaction = 0, - FeePayerTransaction = 1, -} - -/** - * Variants of transaction authenticators used in the system. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L44} - */ -export enum TransactionAuthenticatorVariant { - Ed25519 = 0, - MultiEd25519 = 1, - MultiAgent = 2, - FeePayer = 3, - SingleSender = 4, -} - -/** - * Variants of account authenticators used in transactions. - * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L414} - */ -export enum AccountAuthenticatorVariant { - Ed25519 = 0, - MultiEd25519 = 1, - SingleKey = 2, - MultiKey = 3, -} - -/** - * Variants of private keys that can comply with the AIP-80 standard. - * {@link https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-80.md} - */ -export enum PrivateKeyVariants { - Ed25519 = "ed25519", - Secp256k1 = "secp256k1", -} - -/** - * Variants of public keys used in cryptographic operations. - */ -export enum AnyPublicKeyVariant { - Ed25519 = 0, - Secp256k1 = 1, - Keyless = 3, - FederatedKeyless = 4, -} - -/** - * Variants of signature types used for cryptographic operations. - */ -export enum AnySignatureVariant { - Ed25519 = 0, - Secp256k1 = 1, - Keyless = 3, -} - -/** - * Variants of ephemeral public keys used in cryptographic operations. - */ -export enum EphemeralPublicKeyVariant { - Ed25519 = 0, -} - -/** - * Variants of ephemeral signatures used for secure communication. - */ -export enum EphemeralSignatureVariant { - Ed25519 = 0, -} - -/** - * Variants of ephemeral certificates used in secure transactions. - */ -export enum EphemeralCertificateVariant { - ZkProof = 0, -} - -/** - * Variants of zero-knowledge proofs used in cryptographic operations. - */ -export enum ZkpVariant { - Groth16 = 0, -} - -/** - * BCS types - */ -export type Uint8 = number; - -/** - * A 16-bit unsigned integer. - */ -export type Uint16 = number; - -/** - * A 32-bit unsigned integer. - */ -export type Uint32 = number; - -/** - * A 64-bit unsigned integer value. - */ -export type Uint64 = bigint; - -/** - * A 128-bit unsigned integer used for precise arithmetic operations. - */ -export type Uint128 = bigint; - -/** - * A 256-bit unsigned integer used for precise numerical calculations. - */ -export type Uint256 = bigint; - -/** - * A number or a bigint value. - */ -export type AnyNumber = number | bigint; - -/** - * Configuration options for initializing the SDK, allowing customization of its behavior and interaction with the Aptos network. - */ -export type AptosSettings = { - readonly network?: Network; - - readonly fullnode?: string; - - readonly faucet?: string; - - readonly indexer?: string; - - readonly pepper?: string; - - readonly prover?: string; - - readonly clientConfig?: ClientConfig; - - readonly client?: Client; - - readonly fullnodeConfig?: FullNodeConfig; - - readonly indexerConfig?: IndexerConfig; - - readonly faucetConfig?: FaucetConfig; -}; - -/** - * Defines the parameters for paginating query results, including the starting position and maximum number of items to return. - * @param offset Specifies the starting position of the query result. Default is 0. - * @param limit Specifies the maximum number of items to return. Default is 25. - */ -export interface PaginationArgs { - offset?: AnyNumber; - limit?: number; -} - -/** - * Represents the arguments for specifying a token standard. - * - * @param tokenStandard - Optional standard of the token. - */ -export interface TokenStandardArg { - tokenStandard?: TokenStandard; -} - -export interface OrderByArg { - orderBy?: OrderBy; -} - -export interface WhereArg { - where?: T; -} - -/** - * QUERY TYPES - */ - -/** - * A configuration object for requests to the server, including API key, extra headers, and cookie handling options. - */ -export type ClientConfig = ClientHeadersType & { - WITH_CREDENTIALS?: boolean; - API_KEY?: string; -}; - -/** - * A configuration object for a Fullnode, allowing for the inclusion of extra headers in requests. - */ -export type FullNodeConfig = ClientHeadersType; - -/** - * An Indexer configuration object for sending requests with additional headers. - */ -export type IndexerConfig = ClientHeadersType; - -/** - * A configuration object for a faucet, including optional authentication and headers for requests. - */ -export type FaucetConfig = ClientHeadersType & { - AUTH_TOKEN?: string; -}; - -/** - * General type definition for client headers. - */ -export type ClientHeadersType = { - HEADERS?: Record; -}; - -/** - * Represents a client for making requests to a service provider. - * - * @param Req - The type of the request payload. - * @param Res - The type of the response payload. - */ -export interface ClientRequest { - url: string; - method: "GET" | "POST"; - originMethod?: string; - body?: Req; - contentType?: string; - params?: any; - overrides?: ClientConfig & FullNodeConfig & IndexerConfig & FaucetConfig; - headers?: Record; -} - -export interface ClientResponse { - status: number; - statusText: string; - data: Res; - config?: any; - request?: any; - response?: any; - headers?: any; -} - -export interface Client { - /** - * Sends a request to the specified URL with the given options. - * - * @param requestOptions - The options for the request. - * @param requestOptions.url - The URL to send the request to. - * @param requestOptions.method - The HTTP method to use, either "GET" or "POST". - * @param requestOptions.path - An optional path to append to the URL. - * @param requestOptions.body - The body of the request, applicable for POST requests. - * @param requestOptions.contentType - The content type of the request body. - * @param requestOptions.acceptType - The expected content type of the response. - * @param requestOptions.params - Optional parameters to include in the request. - * @param requestOptions.originMethod - An optional method to specify the origin of the request. - * @param requestOptions.overrides - Optional configuration overrides for the request. - */ - provider(requestOptions: ClientRequest): Promise>; -} - -/** - * The API request type - * - * @param url - the url to make the request to, i.e. https://fullnode.devnet.aptoslabs.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 contentType (optional) - the content type to set the `content-type` header to, - * by default is set to `application/json` - * @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"; - path?: string; - body?: any; - contentType?: string; - acceptType?: string; - params?: Record; - originMethod?: string; - overrides?: ClientConfig & FullNodeConfig & IndexerConfig & FaucetConfig; -}; - -/** - * The ledger version of transactions, defaulting to the latest version if not specified. - */ -export type LedgerVersionArg = { - ledgerVersion?: AnyNumber; -}; - -/** - * RESPONSE TYPES - */ - -/** - * The output of the estimate gas API, including the deprioritized estimate for the gas unit price. - */ -export type GasEstimation = { - /** - * The deprioritized estimate for the gas unit price - */ - deprioritized_gas_estimate?: number; - /** - * The current estimate for the gas unit price - */ - gas_estimate: number; - /** - * The prioritized estimate for the gas unit price - */ - prioritized_gas_estimate?: number; -}; - -export type MoveResource = { - type: MoveStructId; - data: T; -}; - -/** - * The data associated with an account, including its sequence number. - */ -export type AccountData = { - sequence_number: string; - authentication_key: string; -}; - -/** - * A Move module containing an address. - */ -export type MoveModuleBytecode = { - bytecode: string; - abi?: MoveModule; -}; - -/** - * TRANSACTION TYPES - */ - -/** - * Different types of transaction responses that can occur in the system. - */ -export enum TransactionResponseType { - Pending = "pending_transaction", - User = "user_transaction", - Genesis = "genesis_transaction", - BlockMetadata = "block_metadata_transaction", - StateCheckpoint = "state_checkpoint_transaction", - Validator = "validator_transaction", - BlockEpilogue = "block_epilogue_transaction", -} - -/** - * The response for a transaction, which can be either pending or committed. - */ -export type TransactionResponse = PendingTransactionResponse | CommittedTransactionResponse; - -/** - * The response for a committed transaction, which can be one of several transaction types. - */ -export type CommittedTransactionResponse = - | UserTransactionResponse - | GenesisTransactionResponse - | BlockMetadataTransactionResponse - | StateCheckpointTransactionResponse - | ValidatorTransactionResponse - | BlockEpilogueTransactionResponse; - -/** - * Determine if the given transaction response is currently pending. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the transaction is pending. - */ -export function isPendingTransactionResponse(response: TransactionResponse): response is PendingTransactionResponse { - return response.type === TransactionResponseType.Pending; -} - -/** - * Determines if the given transaction response is a user transaction. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the transaction is of type User. - */ -export function isUserTransactionResponse(response: TransactionResponse): response is UserTransactionResponse { - return response.type === TransactionResponseType.User; -} - -/** - * Determines if the given transaction response is a Genesis transaction. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the transaction is a Genesis transaction. - */ -export function isGenesisTransactionResponse(response: TransactionResponse): response is GenesisTransactionResponse { - return response.type === TransactionResponseType.Genesis; -} - -/** - * Determine if the given transaction response is of type BlockMetadata. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the response is a BlockMetadata transaction. - */ -export function isBlockMetadataTransactionResponse( - response: TransactionResponse, -): response is BlockMetadataTransactionResponse { - return response.type === TransactionResponseType.BlockMetadata; -} - -/** - * Determines if the provided transaction response is a state checkpoint transaction. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the transaction response is of type StateCheckpoint. - */ -export function isStateCheckpointTransactionResponse( - response: TransactionResponse, -): response is StateCheckpointTransactionResponse { - return response.type === TransactionResponseType.StateCheckpoint; -} - -/** - * Determine if the given transaction response is of type Validator. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the transaction response is a Validator type. - */ -export function isValidatorTransactionResponse( - response: TransactionResponse, -): response is ValidatorTransactionResponse { - return response.type === TransactionResponseType.Validator; -} - -/** - * Determines if the given transaction response is of the type Block Epilogue. - * - * @param response - The transaction response to evaluate. - * @returns A boolean indicating whether the response is a Block Epilogue transaction. - */ -export function isBlockEpilogueTransactionResponse( - response: TransactionResponse, -): response is BlockEpilogueTransactionResponse { - return response.type === TransactionResponseType.BlockEpilogue; -} - -/** - * The response for a pending transaction, indicating that the transaction is still being processed. - */ -export type PendingTransactionResponse = { - type: TransactionResponseType.Pending; - hash: string; - sender: string; - sequence_number: string; - max_gas_amount: string; - gas_unit_price: string; - expiration_timestamp_secs: string; - payload: TransactionPayloadResponse; - signature?: TransactionSignature; -}; - -/** - * The response structure for a user transaction. - */ -export type UserTransactionResponse = { - type: TransactionResponseType.User; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash: string | null; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - sender: string; - sequence_number: string; - max_gas_amount: string; - gas_unit_price: string; - expiration_timestamp_secs: string; - payload: TransactionPayloadResponse; - signature?: TransactionSignature; - /** - * Events generated by the transaction - */ - events: Array; - timestamp: string; -}; - -/** - * The response for a genesis transaction, indicating the type of transaction. - */ -export type GenesisTransactionResponse = { - type: TransactionResponseType.Genesis; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash?: string; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - payload: GenesisPayload; - /** - * Events emitted during genesis - */ - events: Array; -}; - -/** - * The structure representing a blockchain block with its height. - */ -export type BlockMetadataTransactionResponse = { - type: TransactionResponseType.BlockMetadata; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash: string | null; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - id: string; - epoch: string; - round: string; - /** - * The events emitted at the block creation - */ - events: Array; - /** - * Previous block votes - */ - previous_block_votes_bitvec: Array; - proposer: string; - /** - * The indices of the proposers who failed to propose - */ - failed_proposer_indices: Array; - timestamp: string; -}; - -/** - * The response for a state checkpoint transaction, indicating the type of transaction. - */ -export type StateCheckpointTransactionResponse = { - type: TransactionResponseType.StateCheckpoint; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash: string | null; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - timestamp: string; -}; - -/** - * The response for a validator transaction, indicating the type of transaction. - */ -export type ValidatorTransactionResponse = { - type: TransactionResponseType.Validator; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash: string | null; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - /** - * The events emitted by the validator transaction - */ - events: Array; - timestamp: string; -}; - -/** - * Describes the gas state of the block, indicating whether the block gas limit has been reached. - */ -export type BlockEndInfo = { - block_gas_limit_reached: boolean; - block_output_limit_reached: boolean; - block_effective_block_gas_units: number; - block_approx_output_size: number; -}; - -/** - * A transaction executed at the end of a block that tracks data from the entire block. - */ -export type BlockEpilogueTransactionResponse = { - type: TransactionResponseType.BlockEpilogue; - version: string; - hash: string; - state_change_hash: string; - event_root_hash: string; - state_checkpoint_hash: string | null; - gas_used: string; - /** - * Whether the transaction was successful - */ - success: boolean; - /** - * The VM status of the transaction, can tell useful information in a failure - */ - vm_status: string; - accumulator_root_hash: string; - /** - * Final state of resources changed by the transaction - */ - changes: Array; - timestamp: string; - block_end_info: BlockEndInfo | null; -}; - -/** - * WRITESET CHANGE TYPES - */ - -/** - * A union type that encompasses both script and direct write sets for data operations. - */ -export type WriteSetChange = - | WriteSetChangeDeleteModule - | WriteSetChangeDeleteResource - | WriteSetChangeDeleteTableItem - | WriteSetChangeWriteModule - | WriteSetChangeWriteResource - | WriteSetChangeWriteTableItem; - -/** - * The structure for a module deletion change in a write set. - */ -export type WriteSetChangeDeleteModule = { - type: string; - address: string; - /** - * State key hash - */ - state_key_hash: string; - module: MoveModuleId; -}; - -/** - * The payload for a resource deletion in a write set change. - */ -export type WriteSetChangeDeleteResource = { - type: string; - address: string; - state_key_hash: string; - resource: string; -}; - -/** - * The payload for a write set change that deletes a table item. - */ -export type WriteSetChangeDeleteTableItem = { - type: string; - state_key_hash: string; - handle: string; - key: string; - data?: DeletedTableData; -}; - -/** - * The structure for a write module change in a write set. - */ -export type WriteSetChangeWriteModule = { - type: string; - address: string; - state_key_hash: string; - data: MoveModuleBytecode; -}; - -/** - * The resource associated with a write set change, identified by its type. - */ -export type WriteSetChangeWriteResource = { - type: string; - address: string; - state_key_hash: string; - data: MoveResource; -}; - -/** - * The structure for a write operation on a table in a write set change. - */ -export type WriteSetChangeWriteTableItem = { - type: string; - state_key_hash: string; - handle: string; - key: string; - value: string; - data?: DecodedTableData; -}; - -/** - * The decoded data for a table, including its key in JSON format. - */ -export type DecodedTableData = { - /** - * Key of table in JSON - */ - key: any; - /** - * Type of key - */ - key_type: string; - /** - * Value of table in JSON - */ - value: any; - /** - * Type of value - */ - value_type: string; -}; - -/** - * Data for a deleted table entry. - */ -export type DeletedTableData = { - /** - * Deleted key - */ - key: any; - /** - * Deleted key type - */ - key_type: string; -}; - -/** - * The payload for a transaction response, which can be an entry function, script, or multisig payload. - */ -export type TransactionPayloadResponse = EntryFunctionPayloadResponse | ScriptPayloadResponse | MultisigPayloadResponse; - -/** - * The response payload for an entry function, containing the type of the entry. - */ -export type EntryFunctionPayloadResponse = { - type: string; - function: MoveFunctionId; - /** - * Type arguments of the function - */ - type_arguments: Array; - /** - * Arguments of the function - */ - arguments: Array; -}; - -/** - * The payload for a script response, containing the type of the script. - */ -export type ScriptPayloadResponse = { - type: string; - code: MoveScriptBytecode; - /** - * Type arguments of the function - */ - type_arguments: Array; - /** - * Arguments of the function - */ - arguments: Array; -}; - -/** - * The response payload for a multisig transaction, containing the type of the transaction. - */ -export type MultisigPayloadResponse = { - type: string; - multisig_address: string; - transaction_payload?: EntryFunctionPayloadResponse; -}; - -/** - * The payload for the genesis block containing the type of the payload. - */ -export type GenesisPayload = { - type: string; - write_set: WriteSet; -}; - -/** - * The bytecode for a Move script. - */ -export type MoveScriptBytecode = { - bytecode: string; - abi?: MoveFunction; -}; - -/** - * JSON representations of transaction signatures returned from the node API. - */ -export type TransactionSignature = - | TransactionEd25519Signature - | TransactionSecp256k1Signature - | TransactionMultiEd25519Signature - | TransactionMultiAgentSignature - | TransactionFeePayerSignature; - -/** - * Determine if the provided signature is an Ed25519 signature. - * This function checks for the presence of the "signature" property - * and verifies that its value is "ed25519_signature". - * - * @param signature - The transaction signature to be checked. - * @returns A boolean indicating whether the signature is an Ed25519 signature. - */ -export function isEd25519Signature(signature: TransactionSignature): signature is TransactionFeePayerSignature { - return "signature" in signature && signature.signature === "ed25519_signature"; -} - -/** - * Determine if the provided signature is a valid secp256k1 ECDSA signature. - * - * @param signature - The transaction signature to validate. - * @returns A boolean indicating whether the signature is a secp256k1 ECDSA signature. - */ -export function isSecp256k1Signature(signature: TransactionSignature): signature is TransactionFeePayerSignature { - return "signature" in signature && signature.signature === "secp256k1_ecdsa_signature"; -} - -/** - * Determine if the provided transaction signature is a multi-agent signature. - * - * @param signature - The transaction signature to evaluate. - * @returns A boolean indicating whether the signature is a multi-agent signature. - */ -export function isMultiAgentSignature(signature: TransactionSignature): signature is TransactionMultiAgentSignature { - return signature.type === "multi_agent_signature"; -} - -/** - * Determine if the provided signature is a fee payer signature. - * - * @param signature - The transaction signature to evaluate. - * @returns A boolean indicating whether the signature is a fee payer signature. - */ -export function isFeePayerSignature(signature: TransactionSignature): signature is TransactionFeePayerSignature { - return signature.type === "fee_payer_signature"; -} - -/** - * Determine if the provided signature is of type "multi_ed25519_signature". - * - * @param signature - The transaction signature to check. - * @returns A boolean indicating whether the signature is a multi-ed25519 signature. - */ -export function isMultiEd25519Signature( - signature: TransactionSignature, -): signature is TransactionMultiEd25519Signature { - return signature.type === "multi_ed25519_signature"; -} - -/** - * The signature for a transaction using the Ed25519 algorithm. - */ -export type TransactionEd25519Signature = { - type: string; - public_key: string; - signature: "ed25519_signature"; -}; - -/** - * The structure for a Secp256k1 signature in a transaction. - */ -export type TransactionSecp256k1Signature = { - type: string; - public_key: string; - signature: "secp256k1_ecdsa_signature"; -}; - -/** - * The structure for a multi-signature transaction using Ed25519. - */ -export type TransactionMultiEd25519Signature = { - type: "multi_ed25519_signature"; - /** - * The public keys for the Ed25519 signature - */ - public_keys: Array; - /** - * Signature associated with the public keys in the same order - */ - signatures: Array; - /** - * The number of signatures required for a successful transaction - */ - threshold: number; - bitmap: string; -}; - -/** - * The structure for a multi-agent signature in a transaction. - */ -export type TransactionMultiAgentSignature = { - type: "multi_agent_signature"; - sender: AccountSignature; - /** - * The other involved parties' addresses - */ - secondary_signer_addresses: Array; - /** - * The associated signatures, in the same order as the secondary addresses - */ - secondary_signers: Array; -}; - -/** - * The signature of the fee payer in a transaction. - */ -export type TransactionFeePayerSignature = { - type: "fee_payer_signature"; - sender: AccountSignature; - /** - * The other involved parties' addresses - */ - secondary_signer_addresses: Array; - /** - * The associated signatures, in the same order as the secondary addresses - */ - secondary_signers: Array; - fee_payer_address: string; - fee_payer_signer: AccountSignature; -}; - -/** - * The union of all single account signatures, including Ed25519, Secp256k1, and MultiEd25519 signatures. - */ -export type AccountSignature = - | TransactionEd25519Signature - | TransactionSecp256k1Signature - | TransactionMultiEd25519Signature; - -export type WriteSet = ScriptWriteSet | DirectWriteSet; - -/** - * The set of properties for writing scripts, including the type of script. - */ -export type ScriptWriteSet = { - type: string; - execute_as: string; - script: ScriptPayloadResponse; -}; - -/** - * The set of direct write operations, identified by a type string. - */ -export type DirectWriteSet = { - type: string; - changes: Array; - events: Array; -}; - -/** - * The structure for an event's unique identifier, including its creation number. - */ - -/** - * The structure for an event, identified by a unique GUID. - */ -export type EventGuid = { - creation_number: string; - account_address: string; -}; - -export type Event = { - guid: EventGuid; - sequence_number: string; - type: string; - /** - * The JSON representation of the event - */ - data: any; -}; - -/** - * A number representing a Move uint8 type. - */ -export type MoveUint8Type = number; - -/** - * A 16-bit unsigned integer used in the Move programming language. - */ -export type MoveUint16Type = number; - -/** - * A 32-bit unsigned integer type used in Move programming. - */ -export type MoveUint32Type = number; - -/** - * A string representation of a 64-bit unsigned integer used in Move programming. - */ -export type MoveUint64Type = string; - -/** - * A string representing a 128-bit unsigned integer in the Move programming language. - */ -export type MoveUint128Type = string; - -/** - * A string representation of a 256-bit unsigned integer used in Move programming. - */ -export type MoveUint256Type = string; - -/** - * A string representing a Move address. - */ -export type MoveAddressType = string; - -/** - * The type for identifying objects to be moved within the system. - */ -export type MoveObjectType = string; - -/** - * The type for move options, which can be a MoveType, null, or undefined. - */ -export type MoveOptionType = MoveType | null | undefined; - -/** - * A structure representing a move with a name. - */ -export type MoveStructId = `${string}::${string}::${string}`; - -/** - * The move function containing its name. Same as MoveStructId since it reads weird to take a StructId for a Function. - */ -export type MoveFunctionId = MoveStructId; - -// TODO: Add support for looking up ABI to add proper typing -export type MoveStructType = {}; - -/** - * A union type that encompasses various data types used in Move, including primitive types, address types, object types, and - * arrays of MoveType. - */ -export type MoveType = - | boolean - | string - | MoveUint8Type - | MoveUint16Type - | MoveUint32Type - | MoveUint64Type - | MoveUint128Type - | MoveUint256Type - | MoveAddressType - | MoveObjectType - | MoveStructType - | Array; - -/** - * Possible Move values acceptable by move functions (entry, view) - * - * Map of a Move value to the corresponding TypeScript value - * - * `Bool -> boolean` - * - * `u8, u16, u32 -> number` - * - * `u64, u128, u256 -> string` - * - * `String -> string` - * - * `Address -> 0x${string}` - * - * `Struct - 0x${string}::${string}::${string}` - * - * `Object -> 0x${string}` - * - * `Vector -> Array` - * - * `Option -> MoveValue | null | undefined` - */ -export type MoveValue = - | boolean - | string - | MoveUint8Type - | MoveUint16Type - | MoveUint32Type - | MoveUint64Type - | MoveUint128Type - | MoveUint256Type - | MoveAddressType - | MoveObjectType - | MoveStructId - | MoveOptionType - | Array; - -/** - * A string representation of a Move module, formatted as `module_name::function_name`. - * Module names are case-sensitive. - */ -export type MoveModuleId = `${string}::${string}`; - -/** - * Specifies the visibility levels for move functions, controlling access permissions. - */ -export enum MoveFunctionVisibility { - PRIVATE = "private", - PUBLIC = "public", - FRIEND = "friend", -} - -/** - * Abilities related to moving items within the system. - */ -export enum MoveAbility { - STORE = "store", - DROP = "drop", - KEY = "key", - COPY = "copy", -} - -/** - * Move abilities associated with the generic type parameter of a function. - */ -export type MoveFunctionGenericTypeParam = { - constraints: Array; -}; - -/** - * A field in a Move struct, identified by its name. - */ -export type MoveStructField = { - name: string; - type: string; -}; - -/** - * A Move module - */ -export type MoveModule = { - address: string; - name: string; - /** - * Friends of the module - */ - friends: Array; - /** - * Public functions of the module - */ - exposed_functions: Array; - /** - * Structs of the module - */ - structs: Array; -}; - -/** - * A move struct - */ -export type MoveStruct = { - name: string; - /** - * Whether the struct is a native struct of Move - */ - is_native: boolean; - /** - * Whether the struct is a module event (aka v2 event). This will be false for v1 - * events because the value is derived from the #[event] attribute on the struct in - * the Move source code. This attribute is only relevant for v2 events. - */ - is_event: boolean; - /** - * Abilities associated with the struct - */ - abilities: Array; - /** - * Generic types associated with the struct - */ - generic_type_params: Array; - /** - * Fields associated with the struct - */ - fields: Array; -}; - -/** - * Move function - */ -export type MoveFunction = { - name: string; - visibility: MoveFunctionVisibility; - /** - * Whether the function can be called as an entry function directly in a transaction - */ - is_entry: boolean; - /** - * Whether the function is a view function or not - */ - is_view: boolean; - /** - * Generic type params associated with the Move function - */ - generic_type_params: Array; - /** - * Parameters associated with the move function - */ - params: Array; - /** - * Return type of the function - */ - return: Array; -}; - -/** - * Roles that can be assigned within the system, indicating different levels of access and functionality. - */ -export enum RoleType { - VALIDATOR = "validator", - FULL_NODE = "full_node", -} - -/** - * Information about the current blockchain ledger, including its chain ID. - */ -export type LedgerInfo = { - /** - * Chain ID of the current chain - */ - chain_id: number; - epoch: string; - ledger_version: string; - oldest_ledger_version: string; - ledger_timestamp: string; - node_role: RoleType; - oldest_block_height: string; - block_height: string; - /** - * Git hash of the build of the API endpoint. Can be used to determine the exact - * software version used by the API endpoint. - */ - git_hash?: string; -}; - -/** - * A Block type - */ -export type Block = { - block_height: string; - block_hash: string; - block_timestamp: string; - first_version: string; - last_version: string; - /** - * The transactions in the block in sequential order - */ - transactions?: Array; -}; - -// REQUEST TYPES - -/** - * The request payload for the GetTableItem API. - */ -export type TableItemRequest = { - key_type: MoveValue; - value_type: MoveValue; - /** - * The value of the table item's key - */ - key: any; -}; - -/** - * A list of supported Authentication Key schemes in Aptos, consisting of combinations of signing schemes and derive schemes. - */ -export type AuthenticationKeyScheme = SigningScheme | DeriveScheme; - -/** - * Different schemes for signing keys used in cryptographic operations. - */ -export enum SigningScheme { - /** - * For Ed25519PublicKey - */ - Ed25519 = 0, - /** - * For MultiEd25519PublicKey - */ - MultiEd25519 = 1, - /** - * For SingleKey ecdsa - */ - SingleKey = 2, - - MultiKey = 3, -} - -/** - * Specifies the signing schemes available for cryptographic operations. - */ -export enum SigningSchemeInput { - /** - * For Ed25519PublicKey - */ - Ed25519 = 0, - /** - * For Secp256k1Ecdsa - */ - Secp256k1Ecdsa = 2, -} - -/** - * Specifies the schemes for deriving account addresses from various data sources. - */ -export enum DeriveScheme { - /** - * Derives an address using an AUID, used for objects - */ - DeriveAuid = 251, - /** - * Derives an address from another object address - */ - DeriveObjectAddressFromObject = 252, - /** - * Derives an address from a GUID, used for objects - */ - DeriveObjectAddressFromGuid = 253, - /** - * Derives an address from seed bytes, used for named objects - */ - DeriveObjectAddressFromSeed = 254, - /** - * Derives an address from seed bytes, used for resource accounts - */ - DeriveResourceAccountAddress = 255, -} - -/** - * Options for configuring the behavior of the waitForTransaction() function. - */ -export type WaitForTransactionOptions = { - timeoutSecs?: number; - checkSuccess?: boolean; - // Default behavior is to wait for the indexer. Set this to false to disable waiting. - waitForIndexer?: boolean; -}; - -/** - * Input type to generate an account using the Ed25519 signing scheme. - */ -export type GenerateAccountWithEd25519 = { - scheme: SigningSchemeInput.Ed25519; - legacy: boolean; -}; - -/** - * Input type to generate an account with a Single Signer using Secp256k1. - */ -export type GenerateAccountWithSingleSignerSecp256k1Key = { - scheme: SigningSchemeInput.Secp256k1Ecdsa; - legacy?: false; -}; - -export type GenerateAccount = GenerateAccountWithEd25519 | GenerateAccountWithSingleSignerSecp256k1Key; +export * from "./error"; +export * from "./types"; diff --git a/src/types/keyless.ts b/src/types/keyless.ts index 8fea27df0..5aa728ffa 100644 --- a/src/types/keyless.ts +++ b/src/types/keyless.ts @@ -61,3 +61,22 @@ export type Groth16VerificationKeyResponse = { gamma_abc_g1: [string, string]; gamma_g2: string; }; + +/** + * The response containing the Groth16 verification key, including the alpha_g1 component. + */ +export type PatchedJWKsResponse = { + jwks: { entries: [IssuerJWKS] }; +}; + +/** + * The response containing the Groth16 verification key, including the alpha_g1 component. + */ +export type IssuerJWKS = { + issuer: string; + jwks: [MoveAnyStruct]; +}; + +export type MoveAnyStruct = { + variant: { data: string; type_name: string }; +}; diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 000000000..b72aaa5a6 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,1491 @@ +// Copyright © Aptos Foundation +// SPDX-License-Identifier: Apache-2.0 + +import { Network } from "../utils/apiEndpoints"; +import { OrderBy, TokenStandard } from "./indexer"; + +/** + * Different MIME types used for data interchange in transactions and responses. + */ +export enum MimeType { + /** + * JSON representation, used for transaction submission and accept type JSON output + */ + JSON = "application/json", + /** + * BCS representation, used for accept type BCS output + */ + BCS = "application/x-bcs", + /** + * BCS representation, used for transaction submission in BCS input + */ + BCS_SIGNED_TRANSACTION = "application/x.aptos.signed_transaction+bcs", + BCS_VIEW_FUNCTION = "application/x.aptos.view_function+bcs", +} + +/** + * Hexadecimal data input for functions, supporting both string and Uint8Array formats. + */ +export type HexInput = string | Uint8Array; + +/** + * Variants of type tags used in the system, encompassing various data types and structures. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-core/types/src/language_storage.rs#L27} + */ +export enum TypeTagVariants { + Bool = 0, + U8 = 1, + U64 = 2, + U128 = 3, + Address = 4, + Signer = 5, + Vector = 6, + Struct = 7, + U16 = 8, + U32 = 9, + U256 = 10, + Reference = 254, // This is specifically a placeholder and does not represent a real type + Generic = 255, // This is specifically a placeholder and does not represent a real type +} + +/** + * Variants of script transaction arguments used in Rust, encompassing various data types for transaction processing. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/third_party/move/move-core/types/src/transaction_argument.rs#L11} + */ +export enum ScriptTransactionArgumentVariants { + U8 = 0, + U64 = 1, + U128 = 2, + Address = 3, + U8Vector = 4, + Bool = 5, + U16 = 6, + U32 = 7, + U256 = 8, + Serialized = 9, +} + +/** + * The payload for various transaction types in the system. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/mod.rs#L478} + */ +export enum TransactionPayloadVariants { + Script = 0, + EntryFunction = 2, + Multisig = 3, +} + +/** + * Variants of transactions used in the system. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/mod.rs#L440} + */ +export enum TransactionVariants { + MultiAgentTransaction = 0, + FeePayerTransaction = 1, +} + +/** + * Variants of transaction authenticators used in the system. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L44} + */ +export enum TransactionAuthenticatorVariant { + Ed25519 = 0, + MultiEd25519 = 1, + MultiAgent = 2, + FeePayer = 3, + SingleSender = 4, +} + +/** + * Variants of account authenticators used in transactions. + * {@link https://github.com/aptos-labs/aptos-core/blob/main/types/src/transaction/authenticator.rs#L414} + */ +export enum AccountAuthenticatorVariant { + Ed25519 = 0, + MultiEd25519 = 1, + SingleKey = 2, + MultiKey = 3, +} + +/** + * Variants of private keys that can comply with the AIP-80 standard. + * {@link https://github.com/aptos-foundation/AIPs/blob/main/aips/aip-80.md} + */ +export enum PrivateKeyVariants { + Ed25519 = "ed25519", + Secp256k1 = "secp256k1", +} + +/** + * Variants of public keys used in cryptographic operations. + */ +export enum AnyPublicKeyVariant { + Ed25519 = 0, + Secp256k1 = 1, + Keyless = 3, + FederatedKeyless = 4, +} + +/** + * Variants of signature types used for cryptographic operations. + */ +export enum AnySignatureVariant { + Ed25519 = 0, + Secp256k1 = 1, + Keyless = 3, +} + +/** + * Variants of ephemeral public keys used in cryptographic operations. + */ +export enum EphemeralPublicKeyVariant { + Ed25519 = 0, +} + +/** + * Variants of ephemeral signatures used for secure communication. + */ +export enum EphemeralSignatureVariant { + Ed25519 = 0, +} + +/** + * Variants of ephemeral certificates used in secure transactions. + */ +export enum EphemeralCertificateVariant { + ZkProof = 0, +} + +/** + * Variants of zero-knowledge proofs used in cryptographic operations. + */ +export enum ZkpVariant { + Groth16 = 0, +} + +/** + * BCS types + */ +export type Uint8 = number; + +/** + * A 16-bit unsigned integer. + */ +export type Uint16 = number; + +/** + * A 32-bit unsigned integer. + */ +export type Uint32 = number; + +/** + * A 64-bit unsigned integer value. + */ +export type Uint64 = bigint; + +/** + * A 128-bit unsigned integer used for precise arithmetic operations. + */ +export type Uint128 = bigint; + +/** + * A 256-bit unsigned integer used for precise numerical calculations. + */ +export type Uint256 = bigint; + +/** + * A number or a bigint value. + */ +export type AnyNumber = number | bigint; + +/** + * Configuration options for initializing the SDK, allowing customization of its behavior and interaction with the Aptos network. + */ +export type AptosSettings = { + readonly network?: Network; + + readonly fullnode?: string; + + readonly faucet?: string; + + readonly indexer?: string; + + readonly pepper?: string; + + readonly prover?: string; + + readonly clientConfig?: ClientConfig; + + readonly client?: Client; + + readonly fullnodeConfig?: FullNodeConfig; + + readonly indexerConfig?: IndexerConfig; + + readonly faucetConfig?: FaucetConfig; +}; + +/** + * Defines the parameters for paginating query results, including the starting position and maximum number of items to return. + * @param offset Specifies the starting position of the query result. Default is 0. + * @param limit Specifies the maximum number of items to return. Default is 25. + */ +export interface PaginationArgs { + offset?: AnyNumber; + limit?: number; +} + +/** + * Represents the arguments for specifying a token standard. + * + * @param tokenStandard - Optional standard of the token. + */ +export interface TokenStandardArg { + tokenStandard?: TokenStandard; +} + +export interface OrderByArg { + orderBy?: OrderBy; +} + +export interface WhereArg { + where?: T; +} + +/** + * QUERY TYPES + */ + +/** + * A configuration object for requests to the server, including API key, extra headers, and cookie handling options. + */ +export type ClientConfig = ClientHeadersType & { + WITH_CREDENTIALS?: boolean; + API_KEY?: string; +}; + +/** + * A configuration object for a Fullnode, allowing for the inclusion of extra headers in requests. + */ +export type FullNodeConfig = ClientHeadersType; + +/** + * An Indexer configuration object for sending requests with additional headers. + */ +export type IndexerConfig = ClientHeadersType; + +/** + * A configuration object for a faucet, including optional authentication and headers for requests. + */ +export type FaucetConfig = ClientHeadersType & { + AUTH_TOKEN?: string; +}; + +/** + * General type definition for client headers. + */ +export type ClientHeadersType = { + HEADERS?: Record; +}; + +/** + * Represents a client for making requests to a service provider. + * + * @param Req - The type of the request payload. + * @param Res - The type of the response payload. + */ +export interface ClientRequest { + url: string; + method: "GET" | "POST"; + originMethod?: string; + body?: Req; + contentType?: string; + params?: any; + overrides?: ClientConfig & FullNodeConfig & IndexerConfig & FaucetConfig; + headers?: Record; +} + +export interface ClientResponse { + status: number; + statusText: string; + data: Res; + config?: any; + request?: any; + response?: any; + headers?: any; +} + +export interface Client { + /** + * Sends a request to the specified URL with the given options. + * + * @param requestOptions - The options for the request. + * @param requestOptions.url - The URL to send the request to. + * @param requestOptions.method - The HTTP method to use, either "GET" or "POST". + * @param requestOptions.path - An optional path to append to the URL. + * @param requestOptions.body - The body of the request, applicable for POST requests. + * @param requestOptions.contentType - The content type of the request body. + * @param requestOptions.acceptType - The expected content type of the response. + * @param requestOptions.params - Optional parameters to include in the request. + * @param requestOptions.originMethod - An optional method to specify the origin of the request. + * @param requestOptions.overrides - Optional configuration overrides for the request. + */ + provider(requestOptions: ClientRequest): Promise>; +} + +/** + * The API request type + * + * @param url - the url to make the request to, i.e. https://fullnode.devnet.aptoslabs.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 contentType (optional) - the content type to set the `content-type` header to, + * by default is set to `application/json` + * @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"; + path?: string; + body?: any; + contentType?: string; + acceptType?: string; + params?: Record; + originMethod?: string; + overrides?: ClientConfig & FullNodeConfig & IndexerConfig & FaucetConfig; +}; + +/** + * The ledger version of transactions, defaulting to the latest version if not specified. + */ +export type LedgerVersionArg = { + ledgerVersion?: AnyNumber; +}; + +/** + * RESPONSE TYPES + */ + +/** + * The output of the estimate gas API, including the deprioritized estimate for the gas unit price. + */ +export type GasEstimation = { + /** + * The deprioritized estimate for the gas unit price + */ + deprioritized_gas_estimate?: number; + /** + * The current estimate for the gas unit price + */ + gas_estimate: number; + /** + * The prioritized estimate for the gas unit price + */ + prioritized_gas_estimate?: number; +}; + +export type MoveResource = { + type: MoveStructId; + data: T; +}; + +/** + * The data associated with an account, including its sequence number. + */ +export type AccountData = { + sequence_number: string; + authentication_key: string; +}; + +/** + * A Move module containing an address. + */ +export type MoveModuleBytecode = { + bytecode: string; + abi?: MoveModule; +}; + +/** + * TRANSACTION TYPES + */ + +/** + * Different types of transaction responses that can occur in the system. + */ +export enum TransactionResponseType { + Pending = "pending_transaction", + User = "user_transaction", + Genesis = "genesis_transaction", + BlockMetadata = "block_metadata_transaction", + StateCheckpoint = "state_checkpoint_transaction", + Validator = "validator_transaction", + BlockEpilogue = "block_epilogue_transaction", +} + +/** + * The response for a transaction, which can be either pending or committed. + */ +export type TransactionResponse = PendingTransactionResponse | CommittedTransactionResponse; + +/** + * The response for a committed transaction, which can be one of several transaction types. + */ +export type CommittedTransactionResponse = + | UserTransactionResponse + | GenesisTransactionResponse + | BlockMetadataTransactionResponse + | StateCheckpointTransactionResponse + | ValidatorTransactionResponse + | BlockEpilogueTransactionResponse; + +/** + * Determine if the given transaction response is currently pending. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the transaction is pending. + */ +export function isPendingTransactionResponse(response: TransactionResponse): response is PendingTransactionResponse { + return response.type === TransactionResponseType.Pending; +} + +/** + * Determines if the given transaction response is a user transaction. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the transaction is of type User. + */ +export function isUserTransactionResponse(response: TransactionResponse): response is UserTransactionResponse { + return response.type === TransactionResponseType.User; +} + +/** + * Determines if the given transaction response is a Genesis transaction. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the transaction is a Genesis transaction. + */ +export function isGenesisTransactionResponse(response: TransactionResponse): response is GenesisTransactionResponse { + return response.type === TransactionResponseType.Genesis; +} + +/** + * Determine if the given transaction response is of type BlockMetadata. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the response is a BlockMetadata transaction. + */ +export function isBlockMetadataTransactionResponse( + response: TransactionResponse, +): response is BlockMetadataTransactionResponse { + return response.type === TransactionResponseType.BlockMetadata; +} + +/** + * Determines if the provided transaction response is a state checkpoint transaction. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the transaction response is of type StateCheckpoint. + */ +export function isStateCheckpointTransactionResponse( + response: TransactionResponse, +): response is StateCheckpointTransactionResponse { + return response.type === TransactionResponseType.StateCheckpoint; +} + +/** + * Determine if the given transaction response is of type Validator. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the transaction response is a Validator type. + */ +export function isValidatorTransactionResponse( + response: TransactionResponse, +): response is ValidatorTransactionResponse { + return response.type === TransactionResponseType.Validator; +} + +/** + * Determines if the given transaction response is of the type Block Epilogue. + * + * @param response - The transaction response to evaluate. + * @returns A boolean indicating whether the response is a Block Epilogue transaction. + */ +export function isBlockEpilogueTransactionResponse( + response: TransactionResponse, +): response is BlockEpilogueTransactionResponse { + return response.type === TransactionResponseType.BlockEpilogue; +} + +/** + * The response for a pending transaction, indicating that the transaction is still being processed. + */ +export type PendingTransactionResponse = { + type: TransactionResponseType.Pending; + hash: string; + sender: string; + sequence_number: string; + max_gas_amount: string; + gas_unit_price: string; + expiration_timestamp_secs: string; + payload: TransactionPayloadResponse; + signature?: TransactionSignature; +}; + +/** + * The response structure for a user transaction. + */ +export type UserTransactionResponse = { + type: TransactionResponseType.User; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash: string | null; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + sender: string; + sequence_number: string; + max_gas_amount: string; + gas_unit_price: string; + expiration_timestamp_secs: string; + payload: TransactionPayloadResponse; + signature?: TransactionSignature; + /** + * Events generated by the transaction + */ + events: Array; + timestamp: string; +}; + +/** + * The response for a genesis transaction, indicating the type of transaction. + */ +export type GenesisTransactionResponse = { + type: TransactionResponseType.Genesis; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash?: string; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + payload: GenesisPayload; + /** + * Events emitted during genesis + */ + events: Array; +}; + +/** + * The structure representing a blockchain block with its height. + */ +export type BlockMetadataTransactionResponse = { + type: TransactionResponseType.BlockMetadata; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash: string | null; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + id: string; + epoch: string; + round: string; + /** + * The events emitted at the block creation + */ + events: Array; + /** + * Previous block votes + */ + previous_block_votes_bitvec: Array; + proposer: string; + /** + * The indices of the proposers who failed to propose + */ + failed_proposer_indices: Array; + timestamp: string; +}; + +/** + * The response for a state checkpoint transaction, indicating the type of transaction. + */ +export type StateCheckpointTransactionResponse = { + type: TransactionResponseType.StateCheckpoint; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash: string | null; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + timestamp: string; +}; + +/** + * The response for a validator transaction, indicating the type of transaction. + */ +export type ValidatorTransactionResponse = { + type: TransactionResponseType.Validator; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash: string | null; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + /** + * The events emitted by the validator transaction + */ + events: Array; + timestamp: string; +}; + +/** + * Describes the gas state of the block, indicating whether the block gas limit has been reached. + */ +export type BlockEndInfo = { + block_gas_limit_reached: boolean; + block_output_limit_reached: boolean; + block_effective_block_gas_units: number; + block_approx_output_size: number; +}; + +/** + * A transaction executed at the end of a block that tracks data from the entire block. + */ +export type BlockEpilogueTransactionResponse = { + type: TransactionResponseType.BlockEpilogue; + version: string; + hash: string; + state_change_hash: string; + event_root_hash: string; + state_checkpoint_hash: string | null; + gas_used: string; + /** + * Whether the transaction was successful + */ + success: boolean; + /** + * The VM status of the transaction, can tell useful information in a failure + */ + vm_status: string; + accumulator_root_hash: string; + /** + * Final state of resources changed by the transaction + */ + changes: Array; + timestamp: string; + block_end_info: BlockEndInfo | null; +}; + +/** + * WRITESET CHANGE TYPES + */ + +/** + * A union type that encompasses both script and direct write sets for data operations. + */ +export type WriteSetChange = + | WriteSetChangeDeleteModule + | WriteSetChangeDeleteResource + | WriteSetChangeDeleteTableItem + | WriteSetChangeWriteModule + | WriteSetChangeWriteResource + | WriteSetChangeWriteTableItem; + +/** + * The structure for a module deletion change in a write set. + */ +export type WriteSetChangeDeleteModule = { + type: string; + address: string; + /** + * State key hash + */ + state_key_hash: string; + module: MoveModuleId; +}; + +/** + * The payload for a resource deletion in a write set change. + */ +export type WriteSetChangeDeleteResource = { + type: string; + address: string; + state_key_hash: string; + resource: string; +}; + +/** + * The payload for a write set change that deletes a table item. + */ +export type WriteSetChangeDeleteTableItem = { + type: string; + state_key_hash: string; + handle: string; + key: string; + data?: DeletedTableData; +}; + +/** + * The structure for a write module change in a write set. + */ +export type WriteSetChangeWriteModule = { + type: string; + address: string; + state_key_hash: string; + data: MoveModuleBytecode; +}; + +/** + * The resource associated with a write set change, identified by its type. + */ +export type WriteSetChangeWriteResource = { + type: string; + address: string; + state_key_hash: string; + data: MoveResource; +}; + +/** + * The structure for a write operation on a table in a write set change. + */ +export type WriteSetChangeWriteTableItem = { + type: string; + state_key_hash: string; + handle: string; + key: string; + value: string; + data?: DecodedTableData; +}; + +/** + * The decoded data for a table, including its key in JSON format. + */ +export type DecodedTableData = { + /** + * Key of table in JSON + */ + key: any; + /** + * Type of key + */ + key_type: string; + /** + * Value of table in JSON + */ + value: any; + /** + * Type of value + */ + value_type: string; +}; + +/** + * Data for a deleted table entry. + */ +export type DeletedTableData = { + /** + * Deleted key + */ + key: any; + /** + * Deleted key type + */ + key_type: string; +}; + +/** + * The payload for a transaction response, which can be an entry function, script, or multisig payload. + */ +export type TransactionPayloadResponse = EntryFunctionPayloadResponse | ScriptPayloadResponse | MultisigPayloadResponse; + +/** + * The response payload for an entry function, containing the type of the entry. + */ +export type EntryFunctionPayloadResponse = { + type: string; + function: MoveFunctionId; + /** + * Type arguments of the function + */ + type_arguments: Array; + /** + * Arguments of the function + */ + arguments: Array; +}; + +/** + * The payload for a script response, containing the type of the script. + */ +export type ScriptPayloadResponse = { + type: string; + code: MoveScriptBytecode; + /** + * Type arguments of the function + */ + type_arguments: Array; + /** + * Arguments of the function + */ + arguments: Array; +}; + +/** + * The response payload for a multisig transaction, containing the type of the transaction. + */ +export type MultisigPayloadResponse = { + type: string; + multisig_address: string; + transaction_payload?: EntryFunctionPayloadResponse; +}; + +/** + * The payload for the genesis block containing the type of the payload. + */ +export type GenesisPayload = { + type: string; + write_set: WriteSet; +}; + +/** + * The bytecode for a Move script. + */ +export type MoveScriptBytecode = { + bytecode: string; + abi?: MoveFunction; +}; + +/** + * JSON representations of transaction signatures returned from the node API. + */ +export type TransactionSignature = + | TransactionEd25519Signature + | TransactionSecp256k1Signature + | TransactionMultiEd25519Signature + | TransactionMultiAgentSignature + | TransactionFeePayerSignature; + +/** + * Determine if the provided signature is an Ed25519 signature. + * This function checks for the presence of the "signature" property + * and verifies that its value is "ed25519_signature". + * + * @param signature - The transaction signature to be checked. + * @returns A boolean indicating whether the signature is an Ed25519 signature. + */ +export function isEd25519Signature(signature: TransactionSignature): signature is TransactionFeePayerSignature { + return "signature" in signature && signature.signature === "ed25519_signature"; +} + +/** + * Determine if the provided signature is a valid secp256k1 ECDSA signature. + * + * @param signature - The transaction signature to validate. + * @returns A boolean indicating whether the signature is a secp256k1 ECDSA signature. + */ +export function isSecp256k1Signature(signature: TransactionSignature): signature is TransactionFeePayerSignature { + return "signature" in signature && signature.signature === "secp256k1_ecdsa_signature"; +} + +/** + * Determine if the provided transaction signature is a multi-agent signature. + * + * @param signature - The transaction signature to evaluate. + * @returns A boolean indicating whether the signature is a multi-agent signature. + */ +export function isMultiAgentSignature(signature: TransactionSignature): signature is TransactionMultiAgentSignature { + return signature.type === "multi_agent_signature"; +} + +/** + * Determine if the provided signature is a fee payer signature. + * + * @param signature - The transaction signature to evaluate. + * @returns A boolean indicating whether the signature is a fee payer signature. + */ +export function isFeePayerSignature(signature: TransactionSignature): signature is TransactionFeePayerSignature { + return signature.type === "fee_payer_signature"; +} + +/** + * Determine if the provided signature is of type "multi_ed25519_signature". + * + * @param signature - The transaction signature to check. + * @returns A boolean indicating whether the signature is a multi-ed25519 signature. + */ +export function isMultiEd25519Signature( + signature: TransactionSignature, +): signature is TransactionMultiEd25519Signature { + return signature.type === "multi_ed25519_signature"; +} + +/** + * The signature for a transaction using the Ed25519 algorithm. + */ +export type TransactionEd25519Signature = { + type: string; + public_key: string; + signature: "ed25519_signature"; +}; + +/** + * The structure for a Secp256k1 signature in a transaction. + */ +export type TransactionSecp256k1Signature = { + type: string; + public_key: string; + signature: "secp256k1_ecdsa_signature"; +}; + +/** + * The structure for a multi-signature transaction using Ed25519. + */ +export type TransactionMultiEd25519Signature = { + type: "multi_ed25519_signature"; + /** + * The public keys for the Ed25519 signature + */ + public_keys: Array; + /** + * Signature associated with the public keys in the same order + */ + signatures: Array; + /** + * The number of signatures required for a successful transaction + */ + threshold: number; + bitmap: string; +}; + +/** + * The structure for a multi-agent signature in a transaction. + */ +export type TransactionMultiAgentSignature = { + type: "multi_agent_signature"; + sender: AccountSignature; + /** + * The other involved parties' addresses + */ + secondary_signer_addresses: Array; + /** + * The associated signatures, in the same order as the secondary addresses + */ + secondary_signers: Array; +}; + +/** + * The signature of the fee payer in a transaction. + */ +export type TransactionFeePayerSignature = { + type: "fee_payer_signature"; + sender: AccountSignature; + /** + * The other involved parties' addresses + */ + secondary_signer_addresses: Array; + /** + * The associated signatures, in the same order as the secondary addresses + */ + secondary_signers: Array; + fee_payer_address: string; + fee_payer_signer: AccountSignature; +}; + +/** + * The union of all single account signatures, including Ed25519, Secp256k1, and MultiEd25519 signatures. + */ +export type AccountSignature = + | TransactionEd25519Signature + | TransactionSecp256k1Signature + | TransactionMultiEd25519Signature; + +export type WriteSet = ScriptWriteSet | DirectWriteSet; + +/** + * The set of properties for writing scripts, including the type of script. + */ +export type ScriptWriteSet = { + type: string; + execute_as: string; + script: ScriptPayloadResponse; +}; + +/** + * The set of direct write operations, identified by a type string. + */ +export type DirectWriteSet = { + type: string; + changes: Array; + events: Array; +}; + +/** + * The structure for an event's unique identifier, including its creation number. + */ + +/** + * The structure for an event, identified by a unique GUID. + */ +export type EventGuid = { + creation_number: string; + account_address: string; +}; + +export type Event = { + guid: EventGuid; + sequence_number: string; + type: string; + /** + * The JSON representation of the event + */ + data: any; +}; + +/** + * A number representing a Move uint8 type. + */ +export type MoveUint8Type = number; + +/** + * A 16-bit unsigned integer used in the Move programming language. + */ +export type MoveUint16Type = number; + +/** + * A 32-bit unsigned integer type used in Move programming. + */ +export type MoveUint32Type = number; + +/** + * A string representation of a 64-bit unsigned integer used in Move programming. + */ +export type MoveUint64Type = string; + +/** + * A string representing a 128-bit unsigned integer in the Move programming language. + */ +export type MoveUint128Type = string; + +/** + * A string representation of a 256-bit unsigned integer used in Move programming. + */ +export type MoveUint256Type = string; + +/** + * A string representing a Move address. + */ +export type MoveAddressType = string; + +/** + * The type for identifying objects to be moved within the system. + */ +export type MoveObjectType = string; + +/** + * The type for move options, which can be a MoveType, null, or undefined. + */ +export type MoveOptionType = MoveType | null | undefined; + +/** + * A structure representing a move with a name. + */ +export type MoveStructId = `${string}::${string}::${string}`; + +/** + * The move function containing its name. Same as MoveStructId since it reads weird to take a StructId for a Function. + */ +export type MoveFunctionId = MoveStructId; + +// TODO: Add support for looking up ABI to add proper typing +export type MoveStructType = {}; + +/** + * A union type that encompasses various data types used in Move, including primitive types, address types, object types, and + * arrays of MoveType. + */ +export type MoveType = + | boolean + | string + | MoveUint8Type + | MoveUint16Type + | MoveUint32Type + | MoveUint64Type + | MoveUint128Type + | MoveUint256Type + | MoveAddressType + | MoveObjectType + | MoveStructType + | Array; + +/** + * Possible Move values acceptable by move functions (entry, view) + * + * Map of a Move value to the corresponding TypeScript value + * + * `Bool -> boolean` + * + * `u8, u16, u32 -> number` + * + * `u64, u128, u256 -> string` + * + * `String -> string` + * + * `Address -> 0x${string}` + * + * `Struct - 0x${string}::${string}::${string}` + * + * `Object -> 0x${string}` + * + * `Vector -> Array` + * + * `Option -> MoveValue | null | undefined` + */ +export type MoveValue = + | boolean + | string + | MoveUint8Type + | MoveUint16Type + | MoveUint32Type + | MoveUint64Type + | MoveUint128Type + | MoveUint256Type + | MoveAddressType + | MoveObjectType + | MoveStructId + | MoveOptionType + | Array; + +/** + * A string representation of a Move module, formatted as `module_name::function_name`. + * Module names are case-sensitive. + */ +export type MoveModuleId = `${string}::${string}`; + +/** + * Specifies the visibility levels for move functions, controlling access permissions. + */ +export enum MoveFunctionVisibility { + PRIVATE = "private", + PUBLIC = "public", + FRIEND = "friend", +} + +/** + * Abilities related to moving items within the system. + */ +export enum MoveAbility { + STORE = "store", + DROP = "drop", + KEY = "key", + COPY = "copy", +} + +/** + * Move abilities associated with the generic type parameter of a function. + */ +export type MoveFunctionGenericTypeParam = { + constraints: Array; +}; + +/** + * A field in a Move struct, identified by its name. + */ +export type MoveStructField = { + name: string; + type: string; +}; + +/** + * A Move module + */ +export type MoveModule = { + address: string; + name: string; + /** + * Friends of the module + */ + friends: Array; + /** + * Public functions of the module + */ + exposed_functions: Array; + /** + * Structs of the module + */ + structs: Array; +}; + +/** + * A move struct + */ +export type MoveStruct = { + name: string; + /** + * Whether the struct is a native struct of Move + */ + is_native: boolean; + /** + * Whether the struct is a module event (aka v2 event). This will be false for v1 + * events because the value is derived from the #[event] attribute on the struct in + * the Move source code. This attribute is only relevant for v2 events. + */ + is_event: boolean; + /** + * Abilities associated with the struct + */ + abilities: Array; + /** + * Generic types associated with the struct + */ + generic_type_params: Array; + /** + * Fields associated with the struct + */ + fields: Array; +}; + +/** + * Move function + */ +export type MoveFunction = { + name: string; + visibility: MoveFunctionVisibility; + /** + * Whether the function can be called as an entry function directly in a transaction + */ + is_entry: boolean; + /** + * Whether the function is a view function or not + */ + is_view: boolean; + /** + * Generic type params associated with the Move function + */ + generic_type_params: Array; + /** + * Parameters associated with the move function + */ + params: Array; + /** + * Return type of the function + */ + return: Array; +}; + +/** + * Roles that can be assigned within the system, indicating different levels of access and functionality. + */ +export enum RoleType { + VALIDATOR = "validator", + FULL_NODE = "full_node", +} + +/** + * Information about the current blockchain ledger, including its chain ID. + */ +export type LedgerInfo = { + /** + * Chain ID of the current chain + */ + chain_id: number; + epoch: string; + ledger_version: string; + oldest_ledger_version: string; + ledger_timestamp: string; + node_role: RoleType; + oldest_block_height: string; + block_height: string; + /** + * Git hash of the build of the API endpoint. Can be used to determine the exact + * software version used by the API endpoint. + */ + git_hash?: string; +}; + +/** + * A Block type + */ +export type Block = { + block_height: string; + block_hash: string; + block_timestamp: string; + first_version: string; + last_version: string; + /** + * The transactions in the block in sequential order + */ + transactions?: Array; +}; + +// REQUEST TYPES + +/** + * The request payload for the GetTableItem API. + */ +export type TableItemRequest = { + key_type: MoveValue; + value_type: MoveValue; + /** + * The value of the table item's key + */ + key: any; +}; + +/** + * A list of supported Authentication Key schemes in Aptos, consisting of combinations of signing schemes and derive schemes. + */ +export type AuthenticationKeyScheme = SigningScheme | DeriveScheme; + +/** + * Different schemes for signing keys used in cryptographic operations. + */ +export enum SigningScheme { + /** + * For Ed25519PublicKey + */ + Ed25519 = 0, + /** + * For MultiEd25519PublicKey + */ + MultiEd25519 = 1, + /** + * For SingleKey ecdsa + */ + SingleKey = 2, + + MultiKey = 3, +} + +/** + * Specifies the signing schemes available for cryptographic operations. + */ +export enum SigningSchemeInput { + /** + * For Ed25519PublicKey + */ + Ed25519 = 0, + /** + * For Secp256k1Ecdsa + */ + Secp256k1Ecdsa = 2, +} + +/** + * Specifies the schemes for deriving account addresses from various data sources. + */ +export enum DeriveScheme { + /** + * Derives an address using an AUID, used for objects + */ + DeriveAuid = 251, + /** + * Derives an address from another object address + */ + DeriveObjectAddressFromObject = 252, + /** + * Derives an address from a GUID, used for objects + */ + DeriveObjectAddressFromGuid = 253, + /** + * Derives an address from seed bytes, used for named objects + */ + DeriveObjectAddressFromSeed = 254, + /** + * Derives an address from seed bytes, used for resource accounts + */ + DeriveResourceAccountAddress = 255, +} + +/** + * Options for configuring the behavior of the waitForTransaction() function. + */ +export type WaitForTransactionOptions = { + timeoutSecs?: number; + checkSuccess?: boolean; + // Default behavior is to wait for the indexer. Set this to false to disable waiting. + waitForIndexer?: boolean; +}; + +/** + * Input type to generate an account using the Ed25519 signing scheme. + */ +export type GenerateAccountWithEd25519 = { + scheme: SigningSchemeInput.Ed25519; + legacy: boolean; +}; + +/** + * Input type to generate an account with a Single Signer using Secp256k1. + */ +export type GenerateAccountWithSingleSignerSecp256k1Key = { + scheme: SigningSchemeInput.Secp256k1Ecdsa; + legacy?: false; +}; + +export type GenerateAccount = GenerateAccountWithEd25519 | GenerateAccountWithSingleSignerSecp256k1Key; diff --git a/tests/e2e/api/keyless.test.ts b/tests/e2e/api/keyless.test.ts index 55d0ee7ef..7a44db63f 100644 --- a/tests/e2e/api/keyless.test.ts +++ b/tests/e2e/api/keyless.test.ts @@ -2,7 +2,7 @@ // Copyright © Aptos Foundation // SPDX-License-Identifier: Apache-2.0 -import { Account, FederatedKeylessAccount, KeylessAccount, ProofFetchStatus } from "../../../src"; +import { Account, FederatedKeylessAccount, Hex, KeylessAccount, ProofFetchStatus } from "../../../src"; import { FUND_AMOUNT, TRANSFER_AMOUNT } from "../../unit/helper"; import { getAptosClient } from "../helper"; import { EPHEMERAL_KEY_PAIR, simpleCoinTransactionHeler as simpleCoinTransactionHelper } from "../transaction/helper"; @@ -92,6 +92,19 @@ describe("keyless api", () => { KEYLESS_TEST_TIMEOUT, ); + test("submitting a keyless txn using an outdated JWK should error with meaningful message", async () => { + // This deserializes a keyless account derived from a JWT with a kid that is no longer valid. + const account = KeylessAccount.fromBytes( + Hex.fromHexInput( + "0xda0565794a68624763694f694a53557a49314e694973496e523563434936496b705856434973496d74705a434936496e526c63335174636e4e684d694a392e65794a7063334d694f694a305a584e304c6d39705a474d7563484a76646d6c6b5a5849694c434a68645751694f694a305a584e304c57746c6557786c63334d745a47467763434973496e4e3159694936496e526c6333517464584e6c63694973496d567459576c73496a6f696447567a644542686348527663327868596e4d7559323974496977695a57316861577866646d567961575a705a5751694f6e527964575573496d6c68644349364f5467334e6a55304d7a49774f5377695a586877496a6f354f4463324e54517a4d6a45774c434a756232356a5a534936496a63774f5455794e44497a4d7a4d354e6a51304e5463794e6a63354d7a51334d6a4d334e6a67774f44417a4d444d7a4d6a51304e6a49344d6a45784f5445334e5459304d446b304e5441774f546b314d546b334f4445774e5445354d5441784f4463784d54676966512e526d417a3365455f6156786a4d47464874744b6b557a507677764451755664474667585633566968685937613242386a756b5f50772d4e714c4545674c4473425f5668316a446f507953766f6769454477485a3566546f716b396272496d64666d414377323770722d2d4d51366b6e366e306b32584f506d4d716a51374b454d4d3433526637734b5f39542d67756f7666304956523434734a44716e434a616e5842645a4b35326a4e52766a327a6d6b4d79705659585148417a356a764a6c4351636e5468304d70496d39494f67527a6a4b546b3061783857723949447a7a775f5f6c6a6a303336636c696d57427a68474b4b7739614b49656b373055673668323630346f49384342526c784f4b696d7732344e58494f5f326a5142524d666554575f68496d3971337051314f4d4c2d6637504d47644141795647785f73454d30777759706344666a4245674b315f526752414e5267037375620000000000000000000000000000000000000000000000000000000000000000201111111111111111111111111111111111111111111111111111111111111111443f716700000000000000000000000000000000000000000000000000000000000000000000000097f61c10a645a6429d48edc38939035ce591d90c4facdb0c56e749c1d90b5d85dafda25db344f6ef2618ce9164c968448426feaab0d65cf492923f3136d9c10c484016524e358764ff956f97a51d0e01df330f6619408dd1bbe860211bbcd888168b197e055d23873115a0f3597630db1f03499b427991587aab0e12608a541c809698000000000000000100408ee4b0e9ab9c7b1c87b5ac173f83ccacc9d36a5696a36d1c536906c56ab7db8c3c75f039f70caf483a898b6b34b87559a29e725f4b3331e31134064884b2b80c", + ).toUint8Array(), + ); + const recipient = Account.generate(); + await expect(simpleCoinTransactionHelper(aptos, account, recipient)).rejects.toThrow( + "JWK with kid test-rsa2 for issuer test.oidc.provider not found.", + ); + }); + describe.each([ { jwts: TEST_JWT_TOKENS, jwkAddress: undefined }, { jwts: TEST_FEDERATED_JWT_TOKENS, jwkAddress: jwkAccount.accountAddress }, diff --git a/tests/unit/client.test.ts b/tests/unit/client.test.ts index 94d667bcd..6ac0238e8 100644 --- a/tests/unit/client.test.ts +++ b/tests/unit/client.test.ts @@ -1,5 +1,5 @@ +import { AptosApiError } from "../../src"; import { AptosApiType } from "../../src/utils/const.js"; -import { AptosApiError } from "../../src/client/types.js"; describe(AptosApiError.name, () => { it("should generate pretty error messages", () => {