Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.stablenet.network/llms.txt

Use this file to discover all available pages before exploring further.

Build a two-signature flow where a fee payer account covers gas costs on behalf of a transaction sender.

What you’ll learn

By the end of this tutorial, you’ll be able to:
  • Build and sign a fee-delegated transaction (type 0x16) with ethers.js
  • Add the fee payer’s signature to a partially signed transaction
  • Broadcast the completed transaction and verify gas was charged to the fee payer

How fee delegation works

StableNet’s fee delegation separates transaction signing from gas payment using transaction type 0x16.
RoleSignsPays
Sender (From)Transaction intent (v, r, s)Transfer value only
Fee Payer (FeePayer)Gas consent (fv, fr, fs)gasLimit × gasPrice
Both signatures are required. The node validates both before accepting the transaction into the mempool. After execution, unused gas is refunded to the fee payer — not the sender.
Fee delegation requires the Applepie fork to be active. Transactions of type 0x16 submitted before the fork activate are rejected immediately.

Prerequisites

  • Node.js ≥ 18 and npm install ethers (v6)
  • Two funded testnet accounts: one sender wallet, one fee payer wallet
  • Get testnet WKRC for both accounts at the Faucet

Build and sign the transaction

The sender builds the transaction, sets feePayer to the fee payer’s address, and signs it.
import { ethers } from "ethers";

const SENDER_PRIVATE_KEY = process.env.SENDER_PRIVATE_KEY!;
const FEE_PAYER_PRIVATE_KEY = process.env.FEE_PAYER_PRIVATE_KEY!;

const provider = new ethers.JsonRpcProvider("https://api.test.stablenet.network");
const senderWallet = new ethers.Wallet(SENDER_PRIVATE_KEY, provider);
const feePayerWallet = new ethers.Wallet(FEE_PAYER_PRIVATE_KEY, provider);

// 1. Build the unsigned transaction (type 0x16)
const unsignedTx = {
  type: 0x16,
  chainId: 8283,
  nonce: await provider.getTransactionCount(senderWallet.address),
  to: "0xRecipientAddress",
  value: ethers.parseEther("1.0"),
  gas: 30400n,
  maxFeePerGas: ethers.parseUnits("80000", "gwei"),
  maxPriorityFeePerGas: ethers.parseUnits("27600", "gwei"),
  feePayer: feePayerWallet.address,
  input: "0x",
  accessList: [],
};

// 2. Sender signs
const senderSig = await senderWallet.signTransaction(unsignedTx);

Add the fee payer’s signature

The fee payer receives the partially signed transaction, reviews the contents, and adds their signature.
// 3. Fee payer reviews and signs
const decoded = ethers.Transaction.from(senderSig);

// Verify the transaction before signing — the fee payer is authorizing gas payment
console.log("to:", decoded.to);
console.log("value:", ethers.formatEther(decoded.value), "WKRC");

const feePayerSig = await feePayerWallet.signTransaction(decoded);
The fee payer should verify to, value, data, and gas before signing to avoid paying for unintended operations.

Broadcast the completed transaction

// 4. Broadcast the fully signed transaction
const txResponse = await provider.broadcastTransaction(feePayerSig);
console.log("tx hash:", txResponse.hash);

const receipt = await txResponse.wait();
console.log("Confirmed in block", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed.toString());
tx hash: 0xabc...
Confirmed in block 1234567
Gas used: 21000

Verify gas was charged to the fee payer

Check the fee payer’s balance before and after to confirm gas was deducted from their account, not the sender’s.
const feePayerBalance = await provider.getBalance(feePayerWallet.address);
console.log("Fee payer balance:", ethers.formatEther(feePayerBalance), "WKRC");
On the Explorer, search the transaction hash at explorer.stablenet.network. The feePayer field in the transaction details confirms which account paid gas.

Key invariants

  • tx.origin is always the sender. Existing contracts that check msg.sender or tx.origin work without modification.
  • The fee payer’s balance cannot be used for value transfer — only gas is at risk.
  • Unused gas is refunded to the fee payer automatically.
  • The fee payer’s address is not exposed inside EVM execution — smart contracts cannot detect that fee delegation was used.

Next steps

Fee Delegation Use Case

Build a gasless dApp flow with fee delegation.

RPC API Reference

personal_signRawFeeDelegateTransaction and related methods.

Deploy with Foundry

Deploy the contract your fee payer will interact with.

Listen to Events

Subscribe to events emitted by fee-delegated transactions.