Building SDKs and Client Extensions #311
hayes-mysten
started this conversation in
Technical Discussions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Client extensions are the recommended way to build SDKs in the Sui ecosystem that depend on other
Sui SDKs or need to fetch data from one of the Sui RPC APIs.
At their core, client extensions are just a standardized way to extend the functionality of an
existing client, and a set of best practices to standardize how these extensions are used to keep
things composable.
Extending a client
Base clients in the Sui Typescript SDK (
SuiClient
,SuiGraphQLClient
, andSuiGrpcClient
) can beextended to add custom properties and additional functionality using the
$extend
method.You can also create classes that can register themselves as a client extension:
You can also add multiple extensions at once:
Most client extensions will require a client with specific capabilities. You can restrict the type
of the
client
parameter onregister
to ensure that the client has the necessary capabilities:Core API
The
ClientWithCoreApi
type represents a client that has acore
client extending the abstractCoreClient
class.The
CoreClient
class defines functionality that can work consistently acrossGRPC
,GraphQL
,and
JSON RPC
, and is supported bySuiClient
,SuiGraphQLClient
, andSuiGrpcClient
.Using
ClientWithCoreApi
allows you to write client extensions that will work across all three APIswithout having to specifically handle these different APIs yourself.
The core API currently supports the following methods:
getObject
getObjects
getCoins
getOwnedObjects
getBalance
getAllBalances
getDynamicFields
getDynamicField
getTransaction
executeTransaction
dryRunTransaction
waitForTransaction
getReferenceGasPrice
resolveTransactionPlugin
These methods can be used via the
core
client extension:Patterns for client extensions
Client extensions allow you to attach arbitrary properties to a client, but there are some best
practices we recommend to keep client extensions consistent, and ensure composability with other
SDKs.
Overview
tx
: Methods that create transactions (but don't execute them) should be nested under atx
property
bcs
: Bcs types for your client extension should be nested under abcs
propertycall
: Methods that return move calls to add to an existing transaction should be nested under acall
propertyview
: methods that query the network (either by loading objects, or via dryRun) and return datashould be nested under a
view
propertyTop level methods
Methods directly on the client extension should be imperative methods that directly take action and
return the result of that operation. For example in the
walrus
client extension has methods likewriteBlob
:This method executes transactions, and writes data directly to walrus.
Methods that create transactions
Methods that create transactions should be nested under a
tx
property:These methods can be synchronous or asynchronous, but should return a
Transaction
that is ready tobe executed.
Methods for building transactions
Methods used to construct transactions should be nested under a
call
property:All methods under the
call
property should return synchronously and be compatible withtx.add
.For Asynchronous transaction construction, you can use "async thunks", describe in more detail
below.
View methods
Methods for querying/loading data should be nested under a
view
property:These methods can be asynchronous, and can fetch data view object reads, dryRun transactions,
devInspect, or custom API calls.
BCS Types
BCS type definitions for your client extension should be nested under a
bcs
property:Transaction Construction
Many SDKs and client extensions need to provide methods to construct transactions. To make it easy
to compose transactions that use functionality from multiple SDKs, we recommend exposing methods
under the
call
property of your extension that make it easy to extend existing transactions withbehavior from your SDK.
Thunks
While there are multiple ways your SDK can return things that can be added to a transaction, we
recommend returning
thunks
, which can be used withtx.add
.A thunk is just a function that accepts the
Transaction
as an argument, and then mutates thetransaction as needed:
This example defines a
deleteBlob
thunk that adds a deleteBlob call to the transaction, andreturns the result of that moveCall.
Arguments for tx.moveCall, as well as the object list of tx.transferObjects also work directly with
thunks allowing you to imit the
tx.add
call:If
client.walrus.call.deleteBlob
is the samedeleteBlob
method defined above, the moveCall willautomatically be added, and the result of that moveCall will get transferred.
This pattern makes it easy to compose a single transaction using methods from multiple SDKs.
Async Thunks
The example above used only synchronous calls. Asynchronously constructing transactions presents
issues for web wallets like
Slush
that depend on popups for transaction signing. This is becausebrowsers automatically block popups unless they are triggered directly by user interaction. If you
asynchronously construct a transaction before submitting it to be signed by the user, the popup will
be blocked.
To work around this issue,
tx.add
can accept anasync thunk
which can be used to asynchronouslyadd functionality to a transaction, while still allowing you to synchronously submit the transaction
for signing by a wallet.
To do this, you must ensure that your method returns synchronously, but returns an async thunk:
You can see in this example that the
deleteBlob
method returns synchronously, but returns an asyncthunk that adds the moveCall after asynchronously fetching the packageId.
When
deleteBlob
is used though, we do not await the result of the async thunk, and it can be usedas if it were synchronous.
Internally the
Transaction
builder will create a placeholder result for the async thunk, allowingyou to use it's result before the thunk is resolved.
Consistently using this pattern will ensure that you can construct transactions that will work well
with web wallets like
Slush
.Transaction Execution
If your client extension executes transactions, you might run into issues ensuring that the SDK
works well with wallets in a web app.
To make transaction execution work well with wallets, sponsorship, and other execution patterns, we
recommend that your client extension accepts a
signer
in any method that executes a transaction.The
Signer
class now has asignAndExecuteTransaction
method which can be used to sign andexecute transactions.
It is highly recommended that use use this method rather than using something like
client.core.executeTransaction
to directly execute transactions.Using the signers
signAndExecuteTransaction
method allows users to provide signers that customizeexecution. For wallets this allows the signer to execute the transaction in wallet rather than
directly in the app. For Enoki, the user can provide a signer that sponsors the transaction so users
do not need to pay for gas. The user will also be able to provide custom signer implementations that
handle parallel or serial transaction execution for more efficient transaction execution when
running in a high throughput backend service.
Registering MVR names
TODO
Caching in client extensions
TODO
Beta Was this translation helpful? Give feedback.
All reactions