Gas Station API

Increase user engagement and retention by sponsoring transactions

Overview

Use Cases

Shinami's Gas Station API lets you easily sponsor transactions for your users. SUI is pulled from a fund you create to power these transactions, managed in Shinami's dashboard (see a walkthrough of creating a fund in our our product FAQ).

Shinami facilitates sponsored transactions based on logic determined by you, the app developer. Examples include sponsoring each user's initial transaction(s) for better onboarding or sponsoring transactions of a particular type that you want to encourage.

Apps looking to onboard and retain Web2 users may choose to sponsor all transactions and use Gas Station alongside Shinami's app-controlled Invisible Wallets or user-controlled zkLogin wallets to make Web3 fully unseen. For a game, this allows players to focus on gameplay, while behind the scenes the game creates and modifies game assets in a Sui wallet designated for the player. Players get the benefits of Web3 without friction.

Authentication, Rate Limits, and Error Handling

Authentication

See our Authentication and API Keys guide.

🚧

Security Warning: Do not call Shinami's Gas Station from your frontend.

We strongly recommend having your backend server integrate with Shinami's Gas Station to prevent exposing any sponsorship access keys on the frontend. If these keys are leaked, bad actors have the ability to sponsor transactions from your fund until it has been drained or you disable the key in our dashboard.

Rate Limits

Gas Station access keys have a rate limit of 10 QPS. When you surpass this, we return a JSON-RPC error code -32010. We recommend implementing retries with a backoff to handle any rate limits errors that arise. You can also consider batching multiple Move calls into a single programmable transaction block for sponsorship. They'll execute in sequence, having the same effect as if they were submitted separately.

Error Handling

See our Error Reference for guidance on the errors you may receive from our services, including a section on errors specific to the Gas Station API.

Sponsoring and Executing a Gas-less Transaction

This section provides a technical summary. For a high-level image of the sponsorship flow see here.

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

When a transaction is sponsored, the sponsor sends Gas Station two parts of a TranasactionData object: the sender address and the programmable transaction block to be executed (with gas information is omitted, this is considered a TransactionKind object). Gas Station is responsible for attaching a GasData object that represents the sponsorship. Finally, the BCS-serialized, Base64-encoded string that represents the full TransactionData (with gas information) is produced and sent back along with the sponsor's signature authorizing this transaction. These steps are performed by the gas_sponsorTransactionBlock method below.

From there, all that is needed to call sui_executeTransactionBlock is the sender's signature over the TransactionData.

Our Gas Station TypeScript tutorial has a full example of how to build, sponsor, and execute a sample transaction. Below is a simple example of constructing a TransactionKind containing one Move call that uses the Shinami Clients SDK.

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

import { 
  createSuiClient,
  buildGaslessTransactionBytes
} from "@shinami/clients";

const sui = createSuiClient("{{nodeAccessKey}}");

// this example uses a Move module on Testnet
await buildGaslessTransactionBytes({
  sui,
  build: async (txb) => {
    txb.moveCall({
      target: "0xfa0e78030bd16672174c2d6cc4cd5d1d1423d03c28a74909b2a148eda8bcca16::clock::access",
      arguments: [txb.object('0x6')]
    });
  }
});


// returns: 
{
  "gaslessTx" : "AAABACJaXrXFgMtrbET/1gxNeQIeecWmzqfrPmCWLuX5vGyyEGxlYWRlcmJvYXJkXzgxOTIQbGVhZGVyYm9hcmRfODE5MgAA"
}
// Pass this value as the value for transactionBytes in gasSponsorTransactionBlock

Methods

gas_sponsorTransactionBlock

Description
Sponsor a gas-less transaction. This request takes two parts of a TransactionData object: the sender address and the programmable transaction block to be executed (with gas information is omitted, this is considered a TransactionKindobject). It asks the Gas Station to attach a gas object to it and produce a complete TransactionData object signed by the sponsor (your Gas Station fund). The returned TransactionData then needs to be signed by the sender and sent to Shinami's Node Service (or any full node) with both signatures to be executed with sui_executeTransactionBlock. If you are sponsoring a transaction initiated by an Invisible Wallet, you can sponsor, sign, and execute a transaction in one method call.

You can use gas_getSponsoredTransactionBlockStatus to check the status before attempting to execute the transaction. If a sponsorship has expired and you or another party attempts to execute the associated transaction, it will result in an error. If you still wish to sponsor the action, you can generate and sponsor a new TransactionKind.

You cannot use the gas object in a sponsored transaction 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 an error .

Shinami sponsorship fees: We charge a small fee (in SUI) per sponsorship request to cover our costs. For details, visit the Billing tab in your Shinami dashboard.

Access key creation and assignment: You'll need to create a network specific Gas Station fund in our dashboard before you can create an access key. When you create a gas station key, you assign it to a network (e.g. Testnet) and a fund on that network. If you're managing two apps on one network, each with their own Gas Station fund, the access key you use determines which fund is drawn from.

See our product FAQ for how to set up a fund on Testnet and add free SUI to it if you don't have one.

Auto-budgeting notes

  • 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.
  • In the time between sponsorship and execution, shared objects can change in a way that increases their transaction cost. Therefore, we encourage you to execute sponsored transactions quickly, if possible, to ensure that the sponsorship amount is sufficient. This is why we add a larger buffer on auto-budgeted sponsorships when a shared object is involved. While we believe this buffer will work 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.

Request Parameters

NameTypeDescription
transactionBytesStringBase64 encoded BCS serialized TransactionKind
senderStringSui address of the TransactionKind sender
gasBudgetString(Optional) The gas budget you wish to use for the transaction, in MIST. The transaction will fail if the gas cost exceeds this value.

- If provided, we use the value as the budget of the sponsorship.
- If omitted, we estimate the transaction cost for you. We then add a buffer (5% for non-shared objects, 25% for shared objects) and use that total value as the budget of the sponsorship.

Example Request Template

The TypeScript example uses the Shinami Clients SDK.

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

  curl https://api.shinami.com/gas/v1 \
  -X POST \
  -H 'X-API-Key: {{gasAccessKey}}' \
  -H 'Content-Type: application/json' \
  -d '{ 
          "jsonrpc":"2.0", 
          "method":"gas_sponsorTransactionBlock",
          "params":[
              "{{gaslessTransactionBytes}}",
              "{{senderAddress}}",
              "{{gasBudget}}"
          ],
          "id":1
      }'
import { GasStationClient } from "@shinami/clients";

const gasClient = new GasStationClient("{{gasAccessKey}}");

await gasClient.sponsorTransactionBlock(
    {{gaslessTransactionBytes}},
    {{senderAddress}},
    {{gasBudget}}
);

Example Response

{
    "jsonrpc":"2.0",
    "result" :{
        "txBytes":"AAAAAQBCe5tpWnBOY2pjc+wz7B0Heo1Zj5cSbCJY3KWF60tybgljb2luX2ZsaXAKY2xhaW1fZmVlcwAAL44vPwjxyww1jOTrS7w8XpXnnhmIeecTKvuXScM6uIIBBqTsJrGuzKqeva+7IsFRCsYB7a6LpS34G1SEzn6DwSCoMHQAAAAAACCt2aRuz44LdsjRwsOpaPls3E4N7zVyDIT8AYWkinZQKh1jLUb/cEkQM/78TmOY3OqklD3PYlErTVc3i1q3A7xe6AMAAAAAAABAS0wAAAAAAAA=",
        "txDigest":"HvtKY9RwuE7NC4gLauLFPY3h5qepEy8R7aZHnc4gJu6G",
        "signature":"AAXr9gCHncXfpJiIuQLd1IyPCz/gIGebCeIFMkqollVsGfxVsoU1unAqOiQyvt4Xa18j/WC8dAN1pvf5Wsfnrw65rDp1CB4nXagWW48gUU79IRg3kJf+6erjnVYdenXh+A==",
        "expireAtTime":1695267721
    },
    "id":1
}
{
    "txBytes":"AAAAAQBCe5tpWnBOY2pjc+wz7B0Heo1Zj5cSbCJY3KWF60tybgljb2luX2ZsaXAKY2xhaW1fZmVlcwAAL44vPwjxyww1jOTrS7w8XpXnnhmIeecTKvuXScM6uIIBBqTsJrGuzKqeva+7IsFRCsYB7a6LpS34G1SEzn6DwSCoMHQAAAAAACCt2aRuz44LdsjRwsOpaPls3E4N7zVyDIT8AYWkinZQKh1jLUb/cEkQM/78TmOY3OqklD3PYlErTVc3i1q3A7xe6AMAAAAAAABAS0wAAAAAAAA=",
    "txDigest":"HvtKY9RwuE7NC4gLauLFPY3h5qepEy8R7aZHnc4gJu6G",
    "signature":"AAXr9gCHncXfpJiIuQLd1IyPCz/gIGebCeIFMkqollVsGfxVsoU1unAqOiQyvt4Xa18j/WC8dAN1pvf5Wsfnrw65rDp1CB4nXagWW48gUU79IRg3kJf+6erjnVYdenXh+A==",
    "expireAtTime":1695267721
}

Response Fields

NameTypeDescription
txBytesStringBase64 encoded, BCS serialized TransactionData, which now includes the gas sponsorship data.
txDigestStringBase 58 encoded transaction digest. Store this and use it to call gas_getSponsoredTransactionBlockStatus defined below.
signatureStringBase64 encoded transaction signature from the sponsor (your Gas Station fund wallet).
expireAtTimeIntExpiration time of the assigned gas object, in Unix epoch seconds. All sponsorships created with this method are set to expire one hour after creation.

gas_getSponsoredTransactionBlockStatus

Description
Check the status of a transaction you've sponsored to see if the gas object has been spent or not.

Request Parameters

NameTypeDescription
transactionDigestStringBase58 encoded transaction digest (returned by gasSponsorTransactionBlock)

Example Request Template

The TypeScript example uses the Shinami Clients SDK.

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

curl https://api.shinami.com/gas/v1 \
-X POST \
-H 'X-API-Key: {{gasAccessKey}}' \
-H 'Content-Type: application/json' \
-d '{ 
        "jsonrpc":"2.0", 
        "method":"gas_getSponsoredTransactionBlockStatus", 
        "params":[
            "{{transactionDigest}}"
        ], 
        "id":1
    }'
import { GasStationClient } from "@shinami/clients";

const gasClient = new GasStationClient("{{gasAccessKey}}");

// pass the digest returned by gas_sponsorTransactionBlock
await gas.getSponsoredTransactionBlockStatus({{transactionDigest}});

Example Response

{
  	"jsonrpc":"2.0",
  	"result":"IN_FLIGHT",
  	"id":1
}
"IN_FLIGHT"

Possible Result Values

NameTypeDescription
"IN_FLIGHT"StringA gas object has been assigned and its expireAtTime has not been reached. A transaction execution attempt has not been made.
"INVALID"StringWe did not find a gas object attached to the transaction that was sponsored by the fund this access key is tied to OR The gas object's expireAtTime was reached and it is still unspent.
"COMPLETE"StringA transaction execution attempt was made and the attached gas object was spent. This does not guarantee that the transaction was successfully executed.

gas_getFund

Description

Get the balance for the Gas Station fund tied to the request's API access key. When you create a Gas Station access key, you link it to exactly one Gas Station fund. So, when you make this request, we return the balance for the fund that's tied to the access key you use for the request. To check which fund a Gas Station access key is tied too, see our FAQ.

Request Parameters

none

Example Request Template

The TypeScript example uses the Shinami Clients SDK.

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

curl https://api.shinami.com/gas/v1 \
-X POST \
-H 'X-API-Key: {{gasAccessKey}}' \
-H 'Content-Type: application/json' \
-d '{ 
        "jsonrpc":"2.0", 
        "method":"gas_getFund", 
        "params":[], 
        "id":1
    }'
import { GasStationClient } from "@shinami/clients";

const gasClient = new GasStationClient("{{gasAccessKey}}");

await gasClient.getFund();

Example Response

{
    "id" : 1,
    "jsonrpc" : "2.0",
    "result" : {
        "balance" : 4915573880,
        "depositAddress" : "0x73e9b9188e84fc642290cf84c5b774f78bb49b60327f8fba23ebc4c5cfaf028b",
        "inFlight" : 0,
        "name" : "First test fund",
        "network" : "SUI_TESTNET"
   }
}
{
  network: 'SUI_TESTNET',
  name: 'First test fund',
  balance: 4915573880,
  inFlight: 0,
  depositAddress: '0x73e9b9188e84fc642290cf84c5b774f78bb49b60327f8fba23ebc4c5cfaf028b'
}

Response Fields

NameTypeDescription
networkStringThe network the fund is tied to.
nameStringThe name given to the fund when it was created.
balanceIntThe balance of the fund that is not in-flight, in MIST. (1,000,000,000 MIST = 1 SUI)
inFlightIntThe amount we are currently withholding from your fund for active sponsorships, in MIST. This is what will be used to pay Sui transaction fees and Shinami fees for the sponsorship.
depositAddressString | nullString representing a Sui address if a deposit address has been created for the fund. null if no deposit address has been created. If you do not have a deposit address, follow the steps in our in our FAQ and a deposit address will be automatically generated.