Gas Station (TypeScript)

How to sponsor transactions with Shinami on Sui

Overview

In this tutorial you'll learn how to sponsor transactions with Shinami's Gas Station and submit them for execution on the Sui blockchain. We also cover key tips for using our Gas Station.

For our examples, we'll be constructing our programmable transactions using the Sui TypeScript SDK provided by Mysten. We'll also use the Shinami Clients SDK to make JSON-RPC calls to Shinami Gas Station for transaction sponsorship and Node Service for transaction execution. This is not meant as a complete template for production code, but as a way to show you how to perform all the requests our Gas Station API supports.

What is a sponsored transaction?

When calling sui_executeTransactionBlock a Base64 encoded string is passed in which encodes the transactions to be executed, the sender address, and the gas information for the transaction block. This unit is called a TransactionData object. Along with one or more signatures, it's what's needed to write to the chain.

When a transaction is being sponsored, the sponsor sends Shinami Gas Station two parts of a TransactionData object: the sender address and the transaction block to be executed - with gas information omitted this is considered a TransactionKind object. Gas Station attaches a GasData object from your Gas Station fund that will pay for the transaction. Then, it produces the BCS-serialized, Base64-encoded string that represents the full TransactionData object, and sends it back to you along with the sponsor signature authorizing this transaction. From there, the last piece needed to call sui_executeTransactionBlock is the sender's signature over the TransactionData. We'll go through all of this below.

Gas Station requests must be from your BE

Our Gas Station does not support CORS, so if you attempt to make requests to it from your frontend you'll get a CORS error. We do this because exposing a Gas Station API key on the frontend is a security risk - an attacker could drain the SUI in your Gas Station fund associated with the key by using it to sponsor transactions. For an example flow of combining frontend signing and/or transaction inputs with backend sponsorship, see our Frontend signing + backend sponsorship tutorial.

Understanding errors

Check the error code and message of any errors you get. We outline common errors in our Error Reference - make sure to check out the section specific to Sui Gas Station as well as the "General Error Codes" section at the top that apply to all Shinami services.

Required setup

Notes:

  • This tutorial requires a Shinami account. If you do not have one, you can sign up here.
  • We take you through the process step-by-step below, but if you get stuck you can reach out to us.

1. Create a Shinami Gas Station fund on Testnet

See the Sui Gas Station page of our Help Center for guidance on how to set up a fund on Testnet if you don't already have one. When you create a Testnet fund, we add some SUI to it so you can test immediately.

2. Clone the github repo

Clone the shinami-examples github repo, cd into the shinami-examples/sui/typescript/backend_examples directory, and run npm installto install the dependencies for running the code. Run tsc in your terminal, and if the command isn't found run npm install typescript --save-dev (see other options here ) . If you need to install npm and Node.js, see here. Below, we'll be using the gas_station.ts file in the shinami-examples/sui/typescript/backend_examples/src directory.

3: Create an API access key and copy it into the file.

Create a key

You use API access keys to interact with Shinami's services. For this tutorial, we'll create one access key that has both Node Service and Gas Station rights for Testnet (you could also make two keys, one with only Node Service rights and one with only Gas Station rights). See our Authentication and API Keys guide for info on how to set up a key with rights to multiple services.

Add it to the gas_station.ts file

Once you have your key, use it as the value for the GAS_AND_NODE_TESTNET_ACCESS_KEY constant. You'll see that we immediately use it to instantiate a Gas Station client (for sponsoring transactions) and a Node client (for executing them).

// 2. Copy your Testnet Gas Station and Node Service key value
const GAS_AND_NODE_TESTNET_ACCESS_KEY = "{{gasAndNodeServiceTestnetAccessKey}}";

// 3. Set up your Gas Station and Node Service clients
const nodeClient = createSuiClient(GAS_AND_NODE_TESTNET_ACCESS_KEY);
const gasStationClient = new GasStationClient(GAS_AND_NODE_TESTNET_ACCESS_KEY);

4. Generate a new Keypair and log its secret key for subsequent runs

We need a Sui address to act as the sender, so we'll generate a new KeyPair. On the first run, the secret key will be printed to the console. We'll reuse the same address for all runs, so you can see all the history in one account.

Note: Your app should have its own way to manage accounts and keys. This is just meant as a convenience when doing your initial tests with Gas Station using our tutorial code. Some examples require the sender to have a SUI coin, so maintaining a consistent sender is required.

Here's what you'll do:

  1. Run the code by running the tsc command in a terminal at the root of the shinami-examples/sui/typescript/backend_examples directory to transpile the .ts files in the /src directory to JavaScript.
  2. Run node build/gas_station.js to run the resulting file.
  3. A secret key will be printed to the console, e.g. secretKey: suiprivkey... value.
  4. Before running the code again, replace the call to generateSecretKey() with the value of the secret key.

Initial code:

// 4. Create a KeyPair to act as the sender
async function generateSecretKey() : Promise<string> {
  const keyPair = new Ed25519Keypair();
  console.log("secretKey:", keyPair.getSecretKey())
  return keyPair.getSecretKey();
}

const ENCODED_SECRET_KEY = await generateSecretKey(); 

After running the code, change it to:

// 4. Create a KeyPair to act as the sender
async function generateSecretKey() : Promise<string> {
  const keyPair = new Ed25519Keypair();
  console.log("secretKey:", keyPair.getSecretKey())
  return keyPair.getSecretKey();
}

const ENCODED_SECRET_KEY = "suiprivkey1qqdn8409tumvyq" //await generateSecretKey();
// the value above is too short for a real key

5. Open your Shinami dashboard

Technically not required, but we recommend opening the Sui Gas Station page of your Shinami dashboard and clicking on the "In flight transactions" or "Completed transactions" tab. These can be helpful when testing.

After running the examples below, take a look at these pages. It may take a few moments or clicking "Refresh data", but you'll see the digest of the transaction you sponsored appear. The code you run will execute the transaction, but note that a transaction can remain in the "In flight transactions" table for a minute or two (until our Gas Station updates the status of the associated gas objects).

Code examples

Overview

Below, we'll review some of our sample code functions and how to run them. At a high-level, you'll uncomment just one function that builds a Shinami type GaslessTransaction for sponsorship - e.g. clockMoveCallGaslessTransaction() in the code block below. Then, save any changes you made to the file, run tsc in the shinami-examples/sui/typescript/backend_examples directory to transpile the code to JavaScript, and finally run node build/gas_station.js to run the resulting file. The functions that take arguments require setting values for the associated constant (generally the object id of a SUI coin or other object owned by the sender).

At a high level, we do the same three things each time:

  1. Generate a transaction without gas info (Step 5).
  2. Sponsor, sign, and submit the transaction to the Sui blockchain (Step 6).
  3. Poll our Full node until the transaction is check-pointed and received by the node, then print the status (Step 7).
// 5. Generate the GaslessTransaction for sponsorship 
const gaslessTx =  await
  clockMoveCallGaslessTransaction();
  // clockMoveCallGaslessTransactionAlternateVersion();
  // checkFundBalanceAndDepositIfNeeded(SUI_COIN_TO_DEPOSIT_ID);
  // splitCoinOwnedByGaslessTransaction(COIN_TO_SPLIT_FROM_ID, SENDER_ADDRESS);
  // mergeCoinsGaslessTransaction(COIN_TO_SPLIT_FROM_ID, COIN_TO_MERGE_ID);
  // transferObjectToRecipientGaslessTransaction(OBJ_ID_TO_TRANSFER, RECIPIENT_ADDRESS);

if (gaslessTx) {
  gaslessTx.sender = SENDER_ADDRESS;

  // 6. Sponsor, sign, and execute the transaction
  const txDigest = await sponsorAndExecuteTransactionForKeyPairSender(
    gaslessTx, keyPairFromSecretKey
  );

  // 7. Wait until the node has processed the transaction and print the status
  const txInfo = await nodeClient.waitForTransaction({ 
    digest: txDigest,
    options: { showEffects: true }
  });

  // You can look up the digest in a Sui explorer - make sure to switch to Testnet
  console.log("\ntxDigest: ", txDigest);
  console.log("status:", txInfo.effects?.status.status);
}

The sponsor, sign, and execute flow

The sponsorAndExecuteTransactionForKeyPairSender does the following:

  1. Asks Shinami Gas Station to sponsor the transaction we generated above using the Gas Station fund tied to the access key you're using. Gas Station returns the full transaction - now with gas payment information - as well as the sponsor signature. We use Shinami's auto-budgeting feature because we omit a gasBudget. To learn more about auto-budgeting (and manual budgeting) see the Appendix.
  2. Generates the sender signature.
  3. Submits the full transaction data, along with the sender and sponsor signatures to the Sui blockchain.
async function sponsorAndExecuteTransactionForKeyPairSender(
  gaslessTx: GaslessTransaction, keypair: Ed25519Keypair): Promise<string> {

  //  1. Send the GaslessTransaction to Shinami Gas Station for sponsorship.
  let sponsoredResponse = await gasStationClient.sponsorTransaction(
    gaslessTx // by not setting gaslessTx.gasBudget we take advantage of Shinami auto-budgeting
  );
  console.log("\nsponsorTransactionBlock response (includes sender 'signature' and 'txBytes' with gas info now included):");
  console.log(sponsoredResponse);

  // 2. Sign the full transaction payload with the sender's key.
  let senderSig = await Transaction.from(sponsoredResponse?.txBytes).sign(
    { signer: keypair }
  );

  // 3. Submit the full transaction payload, along with the gas owner 
  // and sender signatures, for execution on the Sui network
  let executeResponse = await nodeClient.executeTransactionBlock({
    transactionBlock: sponsoredResponse?.txBytes,
    signature: [senderSig?.signature, sponsoredResponse?.signature]
  });

  return executeResponse.digest;
}

Sponsor and execute a Move call

Understand the code

The clockMoveCallTransactionKind function

The clockMoveCallGaslessTransaction function builds a TransactionBlock that calls a function on a Move module we've deployed to Testnet. This is a very simple Move module based on a sample Move project from Mysten. We're calling its one function, access, which takes a read-only reference to the sui::clock::Clock instance located at address 0x6 as its single parameter. The function emits a single event that contains the current timestamp obtained from the Clock instance. We print the GaslessTransaction to the console so you can see it's shape.

async function clockMoveCallGaslessTransaction() : Promise<GaslessTransaction> {
  const gaslessTx = await buildGaslessTransaction(
    (txb) => {
      txb.moveCall({
        target: "0xfa0e78030bd16672174c2d6cc4cd5d1d1423d03c28a74909b2a148eda8bcca16::clock::access",
        arguments: [txb.object('0x6')],
      });
    },
    {
      sui: nodeClient
    }
  );
  console.log("\nbuildGaslessTransaction response (your GaslessTransaction for sponsorship):");
  console.log(gaslessTx);

  return gaslessTx
}

Note: building transaction blocks creates requests to your Node Service account, which counts towards your access key's QPS and your daily Node Service request count (and so your bill if you're on a Pay-as-you-go plan). For more information on how Sui's programmable transaction blocks are built, see our Transaction.build() guide.

Run the code

Make sure you've saved your changes to the file from above (adding your API key and setting a fixed private key value). Run tsc in the shinami-examples/sui/typescript/backend_examples directory to transpile the code to JavaScript. Then, run node build/gas_station.js. You'll see a lot of output in the console - we added a lot of printing so you can see the shape of API call responses and function results. Of course, once you understand them you can comment out the console.log() statements.

View the transaction in an explorer

The last thing printed to the console is the digest of the transaction you sponsored. It will be the same value as the txDigest returned by our gasStationClient.sponsorTransactionBlock request, since at that point the digest is created over the full TransactionData that will be submitted to the chain (which includes transaction operations, gas object, and sender address). Your digest will of course be different than my example since the sponsorship gas object and the sender are different:

txDigest:  Ekz4qGTtSrZeznz1VZKgHqKcksY9WCrsfHPpF489aCy3
status: success

You can look up the digest in a Sui explorer like Suivision or Suiscan (make sure you've set the explorer to Testnet first). This is an image from Suivision showing that my transaction emitted one TimeEvent as mentioned above:


Appendix

You can't access the gas coin in a sponsored transaction

In a sponsored transaction, you cannot use the gas object provided by Shinami for other purposes. For example, you cannot write const [coin] = txb.splitCoins(txb.gas,[txb.pure(100)]); because it's accessing txb.gas. If you try to sponsor a TransactionKind that uses the gas object you will get a JSON-RPC -32602 error.


Check a fund's balance and deposit more SUI in the fund

You can use our gas_getFund endpoint to check the balance of the Gas Station fund tied to the request's API access key. We generate a deposit address for the fund when you look it up in the Shinami dashboard - go ahead and do that now (see how). If you don't, the depositAddress will be null - which is why we guard against it in the code below.

Understand the code

Note: In order to run the checkFundBalanceAndDepositIfNeeded function, you'll need to have a SUI coin in the address controlled by the KeyPair we're using as a sender. It takes that coin's object id as its lone argument. A simple options is to use the faucet to send 1 SUI to that address.

The function does the following:

  1. Sets a minimum fund balance (in MIST), above which it will make a deposit.
  2. Calls getFund to obtain the fund information.
  3. Checks that it has a deposit address and if the MIST available for new sponsorships is less than the minimum fund balance. We subtract inFlight from balance, because inFlight is what's already reserved for your existing, active sponsorships.
  4. If the conditions are met, it creates a GaslessTransaction that transfers the coin passed into the function to your fund. Otherwise, it returns underfined so we know not to poll the Full node for a transaction.

Replace all instances of {{name}} with the actual value for that name

async function checkFundBalanceAndDepositIfNeeded(suiCoinObjectIdToDeposit: string):
  Promise<GaslessTransaction | undefined> {
  const MIN_FUND_BALANCE_MIST = 50_000_000_000; // 50 SUI
  const { balance, inFlight, depositAddress } = await gasStationClient.getFund();

  // Deposit address can be null - see our Help Center for how to generate an address: 
  //  https://docs.shinami.com/docs/sui-gas-station-faq
  if (depositAddress && ((balance - inFlight) < MIN_FUND_BALANCE_MIST)) {
    // We're not actually checking it's a SUI coin we're transferring, which you should do.
    // We're also going to sponsor this with the gas fund we're depositing to, which only
    // works if there's a little SUI left.
    return await transferObjectToRecipientGaslessTransaction(
      suiCoinObjectIdToDeposit,
      depositAddress
    );
  }

  console.log("No deposit because no deposit address or a balance above the minimum you've set.");
  return undefined;
}

Run the code

Set the sender-owned SUI coin ID and Un-comment the function

  1. Set the value of SUI_COIN_TO_DEPOSIT_ID to the object ID of a SUI coin owned by the sender. This function is one of the reasons we started the tutorial by having you hard code a private key: so that you had a fixed sender you could send a SUI coin to.
  2. Make sure the checkFundBalanceAndDepositIfNeeded function is the only sample code function uncommented.
// Values for some of the commented out function calls.
// Objects must be owned by the sender controlled by the KeyPair.
const SUI_COIN_TO_DEPOSIT_ID = "0x0d0c06d477"; // too short for an actual ID
const COIN_TO_SPLIT_FROM_ID = "{{SUIcoinObjectID}}";
const COIN_TO_MERGE_ID =  "{{SUIcoinObjectID}}";
const OBJ_ID_TO_TRANSFER = "{{objId}}";
const RECIPIENT_ADDRESS = "{{SuiAddress}}";


// 5. Generate the GaslessTransaction for sponsorship 
const gaslessTx =  await
  // clockMoveCallGaslessTransaction();
  // clockMoveCallGaslessTransactionAlternateVersion();
  checkFundBalanceAndDepositIfNeeded(SUI_COIN_TO_DEPOSIT_ID);
  // splitCoinOwnedByGaslessTransaction(COIN_TO_SPLIT_FROM_ID, SENDER_ADDRESS);
  // mergeCoinsGaslessTransaction(COIN_TO_SPLIT_FROM_ID, COIN_TO_MERGE_ID);
  // transferObjectToRecipientGaslessTransaction(OBJ_ID_TO_TRANSFER, RECIPIENT_ADDRESS);

Run the code

Make sure you've saved your changes to the file from above. Run tsc in the shinami-examples/sui/typescript/backend_examples directory to transpile the code to JavaScript. Then, run node build/gas_station.js. You can look up the digest printed to the console in a Sui explorer like Suivision or Suiscan (make sure you've set the explorer to Testnet first). You can also view the deposit history of your fund on the "Funds" tab of the Sui Gas Station page of your Shinami Dashboard.


Tips for setting your sponsorship budget

Auto-budgeting

When you omit the gasBudget parameter in a sponsorship request using our Gas Station API or our Invisible Wallet API, we estimate the transaction cost for you. We then add a buffer of 5% for non-shared objects and 25% for shared objects. The larger buffer for shared objects is because in the time between sponsorship and execution, shared objects can change in a way that increases their transaction cost due to transactions from other apps and individuals. Therefore, we encourage you to execute sponsored transactions quickly if possible.

While we believe our buffers work well in most cases, we encourage you to monitor the success rate of your auto-budgeted transactions to gauge whether your specific use-case requires manually setting an even larger gasBudget.

As a part of auto-budgeting, we put your transactionBytes through a sui_dryRunTransactionBlock request as a free service before we attempt to sponsor it. This call will generate error messages for certain invalid transactions, such as if the transactionBytes are transferring an object that's not owned by the sender address you provide. We'll return these errors back to you, which should be the same as if you had made a sui_dryRunTransactionBlock request yourself. We do not do this step if you manually budget, so any issues that would be caught by sui_dryRunTransactionBlock will instead produce an error when you try to execute the transaction.

Manual budgeting

When you provide a value for the gasBudget parameter, remember that:

  1. The budget set for a transaction along with a fee will be "held" from your gas credits fund while the transaction is in-flight. Funds withheld cannot be used for new sponsorships. Our sponsorship fee is also a percentage of your gas budget (see full explanation in the "Sui Gas Station" tab of your dashboard's billing page). So, you don't want to set your gas budget unnecessarily high.
  2. However, if your budget is set too low for the chain to process, the transaction will fail, but gas credits will still be deducted for the computation cost of running the failed transaction on the Sui blockchain. Note that the reference gas price and storage price can change over time, so a similar transaction's cost one day might be different on another day.

This ends up being an exercise in fine tuning so your budgeted gas is enough to push a transaction through, but not overly provisioned that you are inefficient in your gas credit usage. One place to review the cost of your successful transactions is in a Sui explorer like Suivision or Suiscan.

You can also use sui_devInspectTransactionBlock to estimate the gas cost needed by the transaction, but note that this request can produce additional requests that affect your billing. Also, sui_devInspectTransactionBlock does not catch all transaction execution errors and should not be used for testing the correctness of a transaction. sui_dryRunTransactionBlock is better for testing transaction correctness, but requires an attached gas object and also can produce extra requests that affect your billing.

There's an overview of how gas fees are calculated here for more detail.


Other Transaction examples

There are more types of transactions than a Move function call like we did above. Below are some additional examples you can sponsor and execute.

To run any one of them, you need to:

  1. Uncomment only that function in Step 5.
  2. Set the required values for the constants that represent its arguments

This is the example for splitCoinOwnedByGaslessTransaction. Note that

const SENDER_ADDRESS = keyPairFromSecretKey.toSuiAddress();
console.log("sender address:", SENDER_ADDRESS);

// Values for some of the commented out function calls.
// Objects must be owned by the sender controlled by the KeyPair.
const SUI_COIN_TO_DEPOSIT_ID = "{{SUIcoinObjectID}}";
const COIN_TO_SPLIT_FROM_ID = "0x0d0c06d477"; // too short for an actual ID
const COIN_TO_MERGE_ID =  "{{SUIcoinObjectID}}";
const OBJ_ID_TO_TRANSFER = "{{objId}}";
const RECIPIENT_ADDRESS = "{{SuiAddress}}";


// 5. Generate the GaslessTransaction for sponsorship 
const gaslessTx =  await
  // clockMoveCallGaslessTransaction();
  // clockMoveCallGaslessTransactionAlternateVersion();
  // checkFundBalanceAndDepositIfNeeded(SUI_COIN_TO_DEPOSIT_ID);
  splitCoinOwnedByGaslessTransaction(COIN_TO_SPLIT_FROM_ID, SENDER_ADDRESS);
  // mergeCoinsGaslessTransaction(COIN_TO_SPLIT_FROM_ID, COIN_TO_MERGE_ID);
  // transferObjectToRecipientGaslessTransaction(OBJ_ID_TO_TRANSFER, RECIPIENT_ADDRESS);

Once you've set things for the transaction you want:

Save your changes. Run tsc in the shinami-examples/sui/typescript/backend_examples directory to transpile the code to JavaScript. Then, run node build/gas_station.js. You can look up the digest printed to the console in a Sui explorer like Suivision or Suiscan (make sure you've set the explorer to Testnet first).


Another way to build a TransactionKind

Above, we called the clockMoveCallGaslessTransaction function, which uses the buildGaslessTransaction helper function in our TypeScript SDK to build the transaction bytes and turn the bytes into a Base64 string in one request. If you have a need, you can also break those two steps up. The clockMoveCallGaslessTransactionAlternateVersion() does this by:

  1. Creating a new Transaction and populating it with the Move call.
  2. Building the transaction (with onlyTransactionKind: true because this will be a sponsored transaction).
  3. Converting the result to a Base64 string
  4. Returning a GaslessTransaction.
async function clockMoveCallGaslessTransactionAlternateVersion() : Promise<GaslessTransaction> {
  let txb = new Transaction();
  txb.moveCall({
    target: "0xfa0e78030bd16672174c2d6cc4cd5d1d1423d03c28a74909b2a148eda8bcca16::clock::access",
    arguments: [txb.object('0x6')]
  });

  // generate the bcs serialized transaction data without any gas object data
  const gaslessPayloadBytes = await txb.build({ client: nodeClient, onlyTransactionKind: true});

  // convert the byte array to a base64 encoded string
  const gaslessPayloadBase64 = btoa(
      gaslessPayloadBytes
          .reduce((data, byte) => data + String.fromCharCode(byte), '')
  );
  console.log("\nTransactionKind base64 string for sponsorship:");
  console.log(gaslessPayloadBase64);

  return {
    txKind: gaslessPayloadBase64,
    sender: SENDER_ADDRESS,
    gasBudget: undefined,
    gasPrice: undefined
  };
}

Note: building transaction blocks creates requests to your Node Service account, which counts towards your access key's QPS and your daily Node Service request count (and so your bill if you're on a Pay-as-you-go plan). For more information on how Sui's programmable transaction blocks are built, see our TransactionBlock.build() guide.

Run the code

Un-comment the function

Make sure the clockMoveCallGaslessTransactionAlternateVersion is the only function un-commented in Step 5:

// 5. Generate the GaslessTransaction for sponsorship 
const gaslessTx =  await
  // clockMoveCallGaslessTransaction();
  clockMoveCallGaslessTransactionAlternateVersion();
  // checkFundBalanceAndDepositIfNeeded(SUI_COIN_TO_DEPOSIT_ID);
  // splitCoinOwnedByGaslessTransaction(COIN_TO_SPLIT_FROM_ID, SENDER_ADDRESS);
  // mergeCoinsGaslessTransaction(COIN_TO_SPLIT_FROM_ID, COIN_TO_MERGE_ID);
  // transferObjectToRecipientGaslessTransaction(OBJ_ID_TO_TRANSFER, RECIPIENT_ADDRESS);

Run the code

Make sure you've saved your changes to the file from above. Run tsc in the shinami-examples/sui/typescript/backend_examples directory to transpile the code to JavaScript. Then, run node build/gas_station.js. You can look up the digest printed to the console in a Sui explorer like Suivision or Suiscan (make sure you've set the explorer to Testnet first).



Check the status of a sponsorship

Why you may not need to check the status of a sponsorship

For many use cases, you will not need to check the status of your sponsorships. This is because when you get a successful response from the gas_sponsorTransactionBlock request, you will immediately get a sender signature and submit it to our Node Service for execution (and therefore you'll know the status).

Even in the case when a long time elapses between sponsoring the transaction and submitting it to the Sui network, there may not be a need to check the status. If the transaction is executed more than an hour after sponsorship, there will be an error with the associated gas object (because we enforce a 1 hour TTL). If you and the user still want to proceed with the transaction, you can just go through the process again to re-build it and re-sponsor it.

Finally, we return the transaction digest of the sponsored transaction in the txDigest response field. This can be used with a sui_getTransactionBlock request of our Sui Node JSON-RPC service to check on the status of the transaction. So, you have the option to check on the status of the transaction directly, rather than indirectly through checking the attached sponsorship.

How to check the status of a sponsorship

All of that said, there may be certain cases where your application needs to check the status of a sponsorship. To use our method for this:

  1. Uncomment out the await checkSponsorshipStatusExample(); line beneath the checkSponsorshipStatusExample function, as shown below.
  2. Save the changes, transpile the code to JavaScript by running the tsc command, and then run the resulting file with node build/gas_station.js
async function checkSponsorshipStatusExample() : Promise<void> {

  const gaslessTx = await clockMoveCallGaslessTransaction();

  gaslessTx.sender = SENDER_ADDRESS;
  const sponsorship = await gasStationClient.sponsorTransaction(gaslessTx);

  const sponsorshipStatus = await gasStationClient.getSponsoredTransactionStatus(
    sponsorship.txDigest
  );
  console.log("sponsored txDigest:", sponsorship.txDigest);
  console.log("Sponsorship Status:", sponsorshipStatus);
}
await checkSponsorshipStatusExample();

Note that the status will likely be IN_FLIGHT even if you've just executed the transaction. This is because it can take a little time after execution for our system to register that the gas object for the transaction has been used. You can read about the possible sponsorship statuses on our Sui Gas Station API doc.


Integrating Gas Station with Shinami zkLogin wallet API

Shinami's zkLogin Wallet API powers user-controlled wallets that are fully compatible with transactions sponsored by Shinami Gas Station. After generating a zkProof, you can create a gas-less transaction, sponsor it with your Gas Station fund with the zkLogin wallet address as the sender, and sign the transaction with with zkWallet's ephemeral private key. For more information on zkLogin and the order of operations for producing and submitting a transaction to Sui, see Mysten's zkLogin doc. For an end-to-end tutorial showing you how to create your first zkLogin Next.js app that integrates with Shinami's Gas Station API and zkLogin Wallet API, see our zkLogin wallet Next.js Tutorial.


Integrating Gas Station with Shinami Invisible Wallets

Shinami Invisible Wallets are app-controlled wallets you can use to abstract away web3 elements like seed phrases, third-party wallet connections, gas fees, and signing popups. They have native integration with Gas Station, including a method that allow you to sponsor, sign, and execute a transaction in one method call. We've created a tutorial for Invisible Wallets that includes using them with Gas Station.