Developer Guide

Solana Sponsored Transfer

Utila enables transaction sponsorship on Solana through the initiateTransaction → solanaSerializedTransaction API flow. Each sponsored transaction requires two signatures: one from the source wallet and another from the designated fee payer.

When the source wallet transfers SPL tokens, the transaction is evaluated by the transaction policy as a standard token transfer, ensuring that your organizational governance applies seamlessly to sponsored transfers - just as it does on EVM and TRON.

📘

By default, Solana multi-signed transactions must be signed by the co-signer. To enable signing such transactions with the mobile app, contact Utila support

The following Python script demonstrates how to transfer 1.0 USDT from a source wallet, with the transaction fees sponsored by a separate fee payer.

from solders.transaction import VersionedTransaction
from spl.token.instructions import transfer, get_associated_token_address, TransferParams, create_associated_token_account
from solders.message import Message
from spl.token.constants import TOKEN_PROGRAM_ID
from solders.pubkey import Pubkey
from solders import null_signer
import base64
import requests

# Utila wallet address, for example AAsGWqT2q3tF6D1hDXwkeB92KjhMq4YnSL8ydHgtuNLK
fee_payer_address_str = "<fee payer address>"

# Utila wallet address, for example gtD3B7eLi6hFk5vzHdWnuUbHdiAPazd5c6JFbwhu78W
sender_address_str = "<USDT sender address>"

# for example: 3jLPC331SdoJKvsrJuQwqa6XqqcErrY2Qxe4dtPx2Roq
recipient_address_str = "<USDT recepient address>"

# for example: 76ce849bc0f8
vault = "<vault id>"

# for example: [email protected]
designated_signer = "<designated signer id>"

amount = 1.0

# USDT mint address
usdt_mint = Pubkey.from_string("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB")

# Derive Associated Token Accounts (ATAs)
sender_data = get_associated_token_address(sender_address, usdt_mint)
recipient_data = get_associated_token_address(recipient_address, usdt_mint)
transfer_instruction = transfer(
        TransferParams(
        amount=int(amount * (10 ** 6)),  # USDT typically uses 6 decimals
        dest=recipient_data,
        owner=sender_address,
        program_id=TOKEN_PROGRAM_ID,
        source=sender_data,
        )
    )
token_account_creation_instruction = create_associated_token_account(
    payer=fee_payer_address,
    owner=recipient_address,
    mint=usdt_mint,
    token_program_id=TOKEN_PROGRAM_ID
)

message = Message(payer=sender_address, instructions=[token_account_creation_instruction, transfer_instruction])
# Build the transaction

tx = VersionedTransaction(keypairs=[null_signer.NullSigner(fee_payer_address), null_signer.NullSigner(sender_address)], message=message)
# Serialize and convert to base64
b64 = base64.b64encode(bytes(tx)).decode('utf-8')
print("Base64 Transaction:", b64)


# Construct the JSON payload
payload = {
    "parent": "vaults/" + vault,
    "details": {
        "solanaSerializedTransaction": {
            "publish": True,
            "validateOnly": True,
            "rawTransaction": b64,
            "network": "networks/solana-mainnet",
            "replaceBlockhash": True
        }
    },
    "designatedSigners": ["users/" + designated_signer]
}

access_token = "<access token, see https://docs.utila.io/reference/authentication#/>"
# Set the headers, including the access token for authorization
headers = {
    "Authorization": f"Bearer {access_token}",
    "Content-Type": "application/json"
}

# Send the POST request
response = requests.post(
    "https://api.utila.io/v1alpha2/vaults/" + vault + "/transactions:initiate",  
    json=payload,
    headers=headers
)