[v10] Ground-up SDK rewrite with tree-shakeable architecture#842
Draft
gregnazario wants to merge 19 commits intomainfrom
Draft
[v10] Ground-up SDK rewrite with tree-shakeable architecture#842gregnazario wants to merge 19 commits intomainfrom
gregnazario wants to merge 19 commits intomainfrom
Conversation
v10 is a complete rewrite of @aptos-labs/ts-sdk targeting modern runtimes (Node 22+, Bun, Deno, browsers) with ESM-only output, subpath exports, and function-first API design. Key changes from v6: - ESM-only (no CJS), plain tsc build (no bundler) - Function-first API: each operation is a standalone importable function - Namespaced Aptos class via composition (not runtime mixins) - Native fetch (drops @aptos-labs/aptos-client dep) - Native btoa/atob (drops js-base64 dep) - Subpath exports: @aptos-labs/ts-sdk/bcs, /crypto, /core, etc. - sideEffects: false for full tree-shaking - Compat layer at @aptos-labs/ts-sdk/compat for gradual migration Architecture (layered, no circular deps): L0: bcs, hex L1: crypto (ed25519, secp256k1, secp256r1, multi-key, keyless) L2: core (AccountAddress, TypeTag, Network, errors) L3: transactions (builder, payloads, authenticators) L4: account (8 account types + factory) L5: client (native fetch HTTP client) L6: api (standalone functions + Aptos facade class) Testing: 403 unit tests + 28 e2e tests (13 native + 15 compat) CI: GitHub Actions workflow with unit, e2e, Bun, Deno, and Playwright jobs
…types
- Auto-fix import ordering across 18 files
- Remove unused imports (APTOS_COIN, GasEstimation, TransactionAuthenticatorEd25519, createAuthKey, AptosApiType, createConfig)
- Replace banned `{}` type with `Record<string, unknown>` in MoveResource and pagination generics
- Prefix unused `coinType` param with underscore
Adds a thin .cjs wrapper at ./compat that enables require() for v6 CJS users migrating to v10. Uses sync require(esm) on Node 22.12+ and falls back to exporting the import() promise on older Node 22.x. No dual-package hazard since both paths load the same ESM module.
- Replace `any` with `unknown`, proper types, and targeted casts across all source and test files (46 warnings → 0) - Introduce lazy constructor interfaces (LazyPublicKeyClass, LazyAccountAddressClass) for forward-declared types to avoid circular deps without resorting to `any` - Restore src/compat/index.cjs and add `require` export condition + build copy step - Update Aptos constructor to accept AptosSettings | AptosConfig - Includes noble-curves/hashes v2 migration and biome formatter fixes
- Broaden vitest include to tests/**/*.test.ts so test:e2e filter finds e2e test files (was only matching tests/unit/) - Remove pnpm-workspace.yaml (moved ignoredBuiltDependencies into package.json) — its presence made pnpm treat v10 as a workspace root, causing examples/web-test pnpm install to skip local devDependencies - Add deno install step to CI before deno task test so node_modules are resolved for manual nodeModulesDir mode
Add pluggable HTTP client interface (Client/ClientRequest/ClientResponse) threaded through all API call sites, with tests for custom client usage. Audit fixes (round 1): - Narrow waitForTransaction polling to only suppress 404 errors - Add URL scheme validation and credential sanitization in errors - Add private key clear() lifecycle for Secp256r1PrivateKey - Change network maps to Partial<Record<Network, string>> for type safety - Remove silently-ignored coinType param from transferCoinTransaction - Fix typos in error messages (authentiate, asyncronous) - Mark AccountAddress constants as static readonly - Use instanceof ParsingError in isValid() instead of unsafe cast - Constant-time comparison for Hex.equals() and AccountAddress.equals() - Remove redundant type casts in Ed25519Account - Change getSigningMessage from async to sync (no await needed) - Fix no-op ternary in compat Aptos constructor - Change default resource limit from 999 to 1000 Audit fixes (round 2): - Fix secp256r1 signature malleability: add lowS:true to sign/verify - Fix p256.Point.CURVE() wrong API → use Point.Fn.ORDER for curve order - Fix double-hash in AbstractedAccount.signTransactionWithAuthenticator - Guard poseidonHash against empty input arrays - Add max iteration guard (1000 pages) to paginateWithCursor - Add MoveVector.MAX_DESERIALIZE_LENGTH (2^20) to prevent DoS - Add clearSensitiveData() to AbstractKeylessAccount for pepper zeroing - Document JWT storage security implications in AbstractKeylessAccount
…stness
- waitForTransaction: swallow only 404/429, re-throw other errors
- buildSimpleTransaction: always fetch ledgerInfo for chain ID
- AbstractedAccount.signTransaction: wrap with AA envelope
- AbstractedAccount.signWithAuthenticator: remove extra SHA3-256
- MultiEd25519Account: use sorted signerIndices for bitmap
- Secp256r1PrivateKey: extend Serializable, add serialize/deserialize
- V1 abstraction authenticator: use serializeBytes for composability
- bigIntToBytesLE: add overflow and negative value checks
- JWT aud: improve error message for array aud values
- Secp256k1/Secp256r1 public keys: validate uncompressed curve points
- EphemeralKeyPair.serialize: check cleared state
- MultiEd25519PublicKey.deserialize: add alignment validation
- AbstractKeylessAccount.verifySignature: document unsupported
- Poseidon error messages: fix ${bytes} → ${bytes.length}
…ness - Add convertSigningMessage to Secp256r1 sign/verify for consistency with Ed25519/Secp256k1 - Use generateSigningMessage in Groth16ProofAndStatement.hash() for proper domain separation - Move serializer pool reset to releaseSerializer for immediate buffer zeroing - Zero intermediate HD key derivation material (digest, CKDPriv data, parent keys) - Type-check JWT header kid field is a string, not just defined - Add JWT claims length validation (iss, aud, uidVal) against on-chain limits - Add bounds checks to MultiEd25519Signature.deserialize - Add JWT header length check in KeylessSignature.deserialize - Compute RSA modulus size dynamically in MoveJWK.toScalar for >2048-bit keys - Wrap atob in try-catch for base64url error handling - Truncate raw input in deserialization error messages to prevent log flooding - Add nonce documentation explaining why it is not zeroed on clear - Fix createBitmap bounds checking (bit value vs loop index) - Fix MoveVector empty non-U8 vector serialization with _isU8 discriminant - Fix AIP-80 prefix parsing to use slice instead of split - Fix ULEB128 decoder to reject continuation bit on terminal byte - Add MAX_KEYS validation to MultiKey constructor
…ormance
Security:
- S2: EphemeralKeyPair uses === undefined for expiryDateSecs (0 is valid)
- S3: Secp256k1 HD derivation zeros HDKey internals after extracting key
- S4: parseJwtHeader size guard + returns only {kid}
- S5: MoveJWK.toScalar RSA modulus size limit (max 512 bytes)
- S6: MultiEd25519 deserialize uses .slice() instead of .subarray()
- S7: Deserialization utils rethrow TypeError/RangeError
- S9: AccountAddress constructor defensive copy via .slice()
- S10: G1Bytes/G2Bytes readonly data + defensive copy
- S11: WebAuthnSignature fields made readonly
Correctness:
- C1: Serialized.toMoveVector uses this.value directly (no re-serialize)
- C2: MultiKey.verifySignature returns false instead of throwing
- P8 fix: convertSigningMessage regex accepts hex with or without 0x prefix
Performance:
- P1: bcsToBytes() uses serializer pool
- S1: serializeAsBytes uses toUint8Array() (safe with pool zeroing)
- P3: serializeU8/serializeBool write directly to buffer
- P4: ULEB128 encoder writes directly to buffer
- P5: validateNumberInRange fast path for plain numbers
- P6: generateSigningMessage caches domain separator hashes
- P7: isCanonicalEd25519Signature reads at offset (no slice)
- P9: ULEB128 decoder uses plain number arithmetic
- P10: AuthenticationKey.fromSchemeAndBytes avoids spread syntax
- P11: MoveVector uses module-level TextEncoder
- P12: MoveString uses module-level TextEncoder
- P13: CKDPriv uses pre-allocated buffer + DataView
- P14: MoveVector.U8 avoids intermediate Array.from
- P15: Groth16Zkp.deserialize reads .data directly
- P16: poseidon.ts hoists BigInt constants
- P17: hexToAsciiString reuses module-level TextDecoder
- P18: Secp256r1Signature reuses Hex when no normalization needed
…ormance Correctness: - MoveVector.deserialize passes isU8 flag for correct script serialization - MultiEd25519PublicKey.verifySignature returns false instead of throwing - Remove redundant double Hex.fromHexInput in authenticator.ts Security: - Add JWT size limits (8192 bytes) to decodeJwtPayload/decodeJwtHeader - Ed25519PrivateKey.fromDerivationPathInner zeros key on constructor failure - Bound domainSeparatorCache to 64 entries with FIFO eviction Performance: - Hoist TextEncoder to module level in poseidon.ts, keyless.ts, abstract-keyless-account.ts - Replace BigInt() calls with cached constants in serializer, deserializer, move-primitives - AccountAddress.isSpecial() uses loop instead of .slice() allocation - AbstractMultiKey.getIndex uses byte comparison instead of toString()
…rformance Correctness: - MoveOption.deserialize rejects vectors with >1 element (BCS contract) - KeylessAccount.serialize guards against cleared sensitive data Security: - Bound HD key derivation path segments to [0, 2^31) to prevent silent truncation in DataView.setUint32 - Add safe-integer guards for bigint→number conversions on timestamp fields (expiryDateSecs, expHorizonSecs) in keyless deserialization Performance: - serializeAsBytes uses toUint8ArrayView() to avoid redundant copy - validateNumberInRange skips BigInt() coercion when bounds already bigint - padAndPackBytesWithLen uses push() instead of concat() - G2Bytes.toProjectivePoint uses subarray+reverse instead of slice+reverse
…erformance - Add recursion depth limit (128) to TypeTag.deserialize() to prevent stack overflow from deeply nested vector/struct payloads - Validate MultiKeySignature bitmap is exactly 4 bytes after deserialization - Add size limits to WebAuthnSignature authenticatorData (2 KB) and clientDataJSON (4 KB) fields during deserialization - Pre-compute byte-to-bigint lookup table in poseidon.ts to avoid per-byte BigInt allocations in bytesToBigIntLE
Security: - StructTag.deserialize passes depth+1 to inner TypeTag deserialization and caps typeArgs count at 32 - WebAuthnSignature.deserialize bounds signature field at 128 bytes - MultiSigTransactionPayload.deserialize validates variant index - Multi-agent/fee-payer secondary signer vectors capped at 255 Performance: - serializeU64/U128/U256 call BigInt(value) once instead of 2-4x - Deserializer uses constructor-built DataView singleton instead of per-call allocations for U16/U32/I8/I16/I32 - TypeTagVector.u8(), aptosCoinStructTag(), stringStructTag() return cached singletons - AccountAddress.toStringWithoutPrefix fast-paths special addresses to skip full hex encoding
Comprehensive README covering installation, quick start (ESM/CJS), architecture overview, full API reference, and custom HTTP client docs. Migration guide with compat layer instructions, breaking changes with before/after examples, and complete v6→v10 method mapping.
Adds a standalone example project under v10/examples/simple-transfer/ that demonstrates the core Aptos transaction flow: generate accounts, fund via faucet, build a transfer, sign & submit, wait for confirmation, and verify the recipient's balance. Includes: - src/main.ts: runnable script showing the full flow on devnet - tests/unit/transfer.test.ts: mocked unit test covering the entire flow - tests/e2e/transfer.e2e.test.ts: real devnet test (skipped unless APTOS_E2E=1)
Unit tests that mock all HTTP should use LOCAL to make intent clear.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
@aptos-labs/ts-sdkin thev10/directory — ESM-only, function-first API, subpath exports,sideEffects: false@aptos-labs/ts-sdk/compatthat preserves the v6 flat-method API (aptos.getAccountInfo(),aptos.transaction.build.simple()) for gradual migration.github/workflows/run-v10-tests.yaml) with 7 parallel jobs: build+lint, unit tests, e2e tests, Bun runtime, Deno runtime, and Playwright browser testsKey design changes from v6
tsc)@aptos-labs/aptos-clientfetchAptosclasssideEffects: false)Bundle size comparison
All sizes measured with esbuild, minified, crypto deps (
@noble/*,@scure/*,poseidon-lite) externalized.Full SDK import (
new Aptos(...))Tree-shaken import (v10 only —
getLedgerInfo+createConfigvia subpath)Raw dist output
Build time comparison
Test coverage
Test plan
v10/**)pnpm build && pnpm check)