Our new TypeScript SDK

Aptos Labs
6 min readDec 5, 2023

--

While we were working with ecosystem builders and devs to enhance our TypeScript SDK features, we found a few issues in its structural design and the lack of customization in its client support. Realizing these issues resulted in a less than optimal DevX, we decided to redesign the TypeScript SDK.

With the great feedback we got from community members on the discussion thread and internal brainstorming we came up with a new TypeScript SDK version that prioritizes the developer experience.

The new TypeScript SDK offers strongly typed APIs and Interfaces, a more intuitive and understandable transaction builder flow, resolution of the API queries to the required destination service, full nested serialization and deserialization support, Move sub-classes to easily serialize and deserialize Move types and customization and configurations support.

The new version of the TypeScript SDK provides an improved, streamlined, and more user-friendly development experience for developers.

Check out the new TypeScript SDK documentation at https://aptos.dev/sdks/new-ts-sdk/

What is new in our TypeScript SDK?

tl;dr

  • AptosConfig config file for users to configure and customize a project
  • Aptos entry point class as the main entry point into Aptos's APIs
  • Transaction builder flow with single signer, sponsor (aka fee payer) and multi agent transactions support.
  • Single/multi key authentication support following AIP-55
  • Secp256k1 key scheme support
  • Proper formatting and parsing of account addresses as defined by AIP-40
  • Local custom types instead of generated types
  • Input arguments as Object
  • Full nested serialization/deserialization support and Move sub-classes to easily serialize and deserialize Move types
  • Support to config a custom http client instance

SDK usage and query the Aptos chain

OLD SDK

In TypeScript SDK V1 we need to instantiate different classes in order to interact with the Aptos chain to perform different operations.

For example, to fetch data from chain we need to instantiate AptosClient to fetch data from Aptos node and/or IndexerClient to fetch data from Aptos indexer or to use the Provider class that holds references for both classes.

In addition, to make a fund request, we need to instantiate FaucetClient and to fetch and/or submit token related operations we need to instantiate AptosToken .

This leads to a very bad developer experience and confusion as the SDK shifts the responsibility to the developers to know what class they need to use.

Moreover, each of those classes uses the same user configuration as for what network to use and some custom request configurations such as headers, auth token, etc.

const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const aptosClient = new AptosClient(NODE_URL)
const indexerClient = new IndexerClient(INDEXER_URL)
const tokenClient = new TokenClient(aptosClient);

//or

const faucetClient = new FaucetClient(NODE_URL, FAUCET_URL);
const provider = new Provider(Network.TESTNET); // holds both AptosClient and IndexerClient
const tokenClient = new TokenClient(provider.aptosClient); // v1 standard
const aptosToken = new AptosToken(provider); // v2 standard

NEW SDK

In the new TypeScript SDK, we introduce a global AptosConfig config file as a convenient and flexible way for end users to configure and customize a project without directly initialize different classes in the SDK. This reduces the chances of introducing errors and simplifies maintenance. It also makes it easier to share and distribute project configurations across different environments.

In addition, we use an entry point class as the main entry point into Aptos’s APIs. To use the SDK, we create a new Aptos instance to get access to all the SDK functionality.

const config = new AptosConfig({ network: Network.TESTNET });
const aptos = new Aptos(config);

// make queries
const fund = await aptos.fundAccount({ accountAddress: "0x123", amount: 100 })
const account = await aptos.getAccountInfo({ accountAddress: "0x123" });
const tokens = await aptos.getAccountOwnedTokens({ accountAddress: "0x123" });

Transaction Builder Flow

OLD SDK

TypeScript SDK V1 does not really have a “transaction builder flow” but instead it exposes different functions to support different transaction flows. Even then, those functions are very confusing. For example, generateTransaction() supports only JSON format arguments input while generateRawTransaction() supports only BCS serialized arguments input it lacks of support different transaction types such as sponsor and multi agent transactions.

NEW SDK

The new TypeScript SDK aims to simplify the transaction builder flow by introducing separate namespaces for each transaction builder flow step and more meaningful and consolidate functions.

One function to build a transaction with non-serialized arguments and with BCS serialized arguments

const alice: Account = Account.generate();
const bobAddress = "0xb0b";

// build a transaction with non-serialized arguments
const transaction = await aptos.build.transaction({
sender: alice.accountAddress,
data: {
function: "0x1::coin::transfer",
typeArguments: ["0x1::aptos_coin::AptosCoin"],
functionArguments: [bobAddress, 100],
},
});

// build a transaction with BCS serialized arguments
const transaction = await aptos.build.transaction({
sender: alice.accountAddress,
data: {
function: "0x1::coin::transfer",
typeArguments: [parseTypeTag("0x1::aptos_coin::AptosCoin")],
functionArguments: [bobAddress, new U64(100)],
},
});

Build, sign and submit transaction

const alice: Account = Account.generate();
const bobAddress = "0xb0b";

const transaction = await aptos.build.transaction({
sender: alice.accountAddress,
data: {
function: "0x1::coin::transfer",
typeArguments: ["0x1::aptos_coin::AptosCoin"],
functionArguments: [bobAddress, 100],
},
});

// using sign and submit separately
const senderAuthenticator = aptos.sign.transaction({ signer: alice, transaction });
const committedTransaction = await aptos.submit.transaction({ transaction, senderAuthenticator });

// using signAndSubmit combined
const committedTransaction = await aptos.signAndSubmitTransaction({ signer: alice, transaction });

Key Management

OLD SDK

TypeScript SDK V1 supports Ed25519 and MultiEd25519 key scheme. That means that any new keys generated by the SDK can only be Ed25519 and any keys operations support only Ed25519.

const account = new AptosAccount(); // supports only Legacy Ed25519

NEW SDK

Following AIP-55 Aptos supports Legacy and Unified authentications. Legacy includes ED25519 and MultiED25519 and Unified includes SingleSender and MultiSender

  • SingleSender supports ED25519 and Secp256k1
  • MultiSender supports MultiED25519.

The new TypeScript SDK supports Legacy and Unified authenticators while defaulting to Legacy Ed25519 as the community adopts the new authenticators.

const account = Account.generate(); // defaults to Legacy Ed25519
const account = Account.generate({ scheme: SingingSchemeInput.Secp256k1 }); // Single Sender Secp256k1
const account = Account.generate({ scheme: SingingSchemeInput.Ed25519, legacy: false }); // Single Sender Ed25519

Types

OLD SDK

TypeScript SDK V1 uses a combination of pre-generated types using openAPI and local custom types. The inclusion of both types generated by a server schema and locally defined, such as Types and TxnBuilderTypes, has contributed to a lot of confusion. Furthermore, there is room for improvement in the naming conventions for these types, such as introducing more descriptive and intuitive names other than MaybeHexString.

NEW SDK

The new TypeScript SDK defines and holds all types locally and not using any external type generator excluding for Indexer GraphQL schema that even then the SDK customizes the generated types to be more user friendly and understandable.

Also, types are exported on the top level, i.e no namespaces, to reduce confusion on where types are located in the SDK.

Move sub-classes to easily serialize and deserialize Move types

OLD SDK

TypeScript SDK V1 does not have a great support for complex serialization and deserialization for structs or nested vectors. In addition, it is not intuitive how to use BCS functions and what function to use for serialization or deserialization

This is how one would build a transaction payload to rotate auth key

const payload = new TxnBuilderTypes.TransactionPayloadEntryFunction(
TxnBuilderTypes.EntryFunction.natural(
"0x1::account",
"rotate_authentication_key",
[],
[
bcsSerializeU8(0), // ed25519 scheme
bcsSerializeBytes(forAccount.pubKey().toUint8Array()),
bcsSerializeU8(0), // ed25519 scheme
bcsSerializeBytes(helperAccount.pubKey().toUint8Array()),
bcsSerializeBytes(proofSignedByCurrentPrivateKey.toUint8Array()),
bcsSerializeBytes(proofSignedByNewPrivateKey.toUint8Array()),
],
),
);

NEW SDK

The new TypeScript SDK provides full support for complex and in-depth serialization and deserialization of Move types. In addition, we created a Move sub-class for each Move type that matches smart contract argument. This makes it easier to handle and to understand what type we need to work with.

Build a rotate auth key transaction in SDK V2

const singleSignerTransaction = await aptos.build.transaction({
sender: account.accountAddress,
data: {
function: "0x1::account::rotate_authentication_key",
functionArguments: [
new U8(fromAccount.signingScheme.valueOf()), // from scheme
MoveVector.U8(fromAccount.publicKey.toUint8Array()),
new U8(newAccount.signingScheme.valueOf()), // to scheme
MoveVector.U8(newAccount.publicKey.toUint8Array()),
MoveVector.U8(proofSignedByCurrentPrivateKey.toUint8Array()),
MoveVector.U8(proofSignedByNewPrivateKey.toUint8Array()),
],
},
});

Custom http client instance configuration

The Typescript SDK uses a fix http client with the ability to modify some request configurations like AUTH_TOKEN, HEADERS, etc.

Sometimes developers want to set custom configurations or use a specific http client to use for queries.

For that, The new TypeScript SDK adds support to config a custom client.

The custom client is a function with this signature

<Req, Res>(requestOptions: ClientRequest<Req>): Promise<ClientResponse<Res>>

Both ClientRequest and ClientResponse are types defined in the SDK.

async function customClient<Req, Res>(requestOptions: ClientRequest<Req>): Promise<ClientResponse<Res>> {
....
}

const config = new AptosConfig({ client: { provider: customClient } });
const aptos = new Aptos(config);

--

--

Aptos Labs
Aptos Labs

Written by Aptos Labs

Aptos Labs is a premier Web3 studio of engineers, researchers, strategists, designers, and dreamers building on Aptos, the Layer 1 blockchain.