Our new TypeScript SDK
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 projectAptos
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
supportsED25519
andSecp256k1
MultiSender
supportsMultiED25519
.
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);