Skip to main content

Documentation Index

Fetch the complete documentation index at: https://flashnet-build.mintlify.app/llms.txt

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

ExecutionClient.execute runs an arbitrary pre-signed EVM transaction inside an intent. Use this when TradingClient doesn’t cover what you’re doing: calling your own contract, deploying, or interacting with a non-AMM Flashnet contract.

Sign and submit

import { ExecutionClient } from "@flashnet/sdk";
import { fetchNonce } from "@flashnet/sdk";
import { encodeFunctionData } from "viem";

const execClient = new ExecutionClient(sparkWallet, {
  gatewayUrl,
  rpcUrl,
  chainId,
});
await execClient.authenticate();

const account = await execClient.getEvmAccount();
const { execution } = await execClient.getNetworkInfo();
const nonce = await fetchNonce(rpcUrl, account.address);

const calldata = encodeFunctionData({
  abi: myContractAbi,
  functionName: "doThing",
  args: [42n],
});

const signedTx = await account.signTransaction({
  chainId: execution.chainId,
  nonce,
  to: myContractAddress,
  value: 0n,
  data: calldata,
  gas: 500_000n,
  maxFeePerGas: 0n,         // zero-gas, see below
  maxPriorityFeePerGas: 0n,
  type: "eip1559",
});

const result = await execClient.execute({ signedTx });
signedTx is the RLP-encoded EIP-1559 transaction, 0x-prefixed.

Bundle deposits with execution

Pass deposits alongside the signed tx to credit Spark transfers in the same intent that runs the call. Each deposit references an existing Spark transfer by id; the caller is responsible for issuing the Spark-side transfer first (execute does not touch the SparkWallet).
const { spark } = await execClient.getNetworkInfo();
const transfer = await sparkWallet.transfer({
  amountSats: 100_000,
  receiverSparkAddress: spark.depositAddress,
});
const sparkTransferId = transfer.id.replace(/-/g, "");

await execClient.execute({
  deposits: [
    { sparkTransferId, amount: 100_000n, asset: { type: "btc" } },
  ],
  signedTx,
});
The intent enters oracle_pending until the deposit oracle confirms every transfer id against the Spark operator DB. Deposits land before the EVM transaction runs, so doThing can spend the just-credited balance. For the single-intent round-trip (Spark → EVM → Spark) without managing the transfer leg yourself, use TradingClient.swap({ useAvailableBalance: true }).

Zero gas

Flashnet runs baseFeePerGas = 0 and maxPriorityFeePerGas = 0 for every block. The operator covers inclusion. Set both fee fields on your transaction to 0n. Viem’s estimateFeesPerGas defaults will inject non-zero floors that fight you; pass zero explicitly. fetchEip1559Fees(rpcUrl) queries the node and returns the values it currently reports, so a future fee-policy change surfaces here without breaking clients. Note: it ships under the /execution subpath of the SDK barrel, not the root.
import { fetchEip1559Fees } from "@flashnet/sdk/execution";

const fees = await fetchEip1559Fees(rpcUrl);
// { maxFeePerGas: 0n, maxPriorityFeePerGas: 0n } on Flashnet today
Native value (the value field on a transaction) is denominated in 18-decimal wei. One sat is 10_000_000_000 wei (WEI_PER_SAT). Setting value: 100_000n sends 100,000 wei (a tiny fraction of a sat), not 100,000 sats. Multiply by WEI_PER_SAT when you mean sats.

Decoding reverts

When an execute intent’s transaction reverts, the gateway sets status to rejected and packs the revert output into statusMessage:
payload_flagged:stage=Execute outcome=Reverted intent_index=0 \
  tx_hash=0x... reason=reverted:gas_used=189179 output=0x1f2a2005
decodeRevertReason translates the selector back to a name:
import { decodeRevertReason } from "@flashnet/sdk";

const decoded = decodeRevertReason(intent.statusMessage);
console.log(decoded?.name); // "ZeroAmount()"
The default lookup table covers SparkGateway and Conductor errors plus Solidity built-ins. For your own contracts, pass extraErrors:
const decoded = decodeRevertReason(intent.statusMessage, {
  extraErrors: { "0xabcd1234": "MyContractError(uint256)" },
});
For a richer view, traceInnermostRevert walks the call trace and returns the deepest revert frame:
import { traceInnermostRevert } from "@flashnet/sdk/execution";

const frame = await traceInnermostRevert(directRpcUrl, txHash);
if (frame) {
  console.log(frame.address, frame.output, frame.revertReason);
}
The frame returns address, output (ABI-encoded revert bytes), depth, and optionally error and revertReason. There is no input field. traceInnermostRevert uses debug_traceTransaction, which the gateway’s RPC proxy explicitly blocks. Point directRpcUrl at a full-node RPC that exposes the debug namespace (the localnet sequencer does; production typically uses an internal node). Calling against the gateway URL returns null.

Status lifecycle

accepted -> oracle_pending? -> included_pending_finality -> finalized
                                                         \-> rejected
                                                         \-> expired
StageWhat happened
acceptedSignature and expiry checked. Queued for sequencer admission.
oracle_pendingDeposit-shaped intent waiting for Spark operator-DB confirmation on every referenced transfer. Skipped when deposits is empty.
included_pending_finalitySequencer included the tx in a block. The runtime ran (the tx may already have reverted), settlement is still pending.
finalizedValidators signed the finality certificate. State is durable.
rejectedAdmission failure, deposit oracle failure, or transaction revert. statusMessage carries the reason.
expiredThe intent’s expiresAt passed before any terminal state was reached.
A reverted transaction does not roll back deposits. The Spark transfers funding the intent are still credited to the EVM address; they just don’t get consumed by the call. Sweep them back to Spark with withdraw or withdrawToken if needed.

When to use this vs TradingClient

Use TradingClientUse execute
Trading on Flashnet poolsCalling your own contract
Deposit + swap + withdraw atomicallyDeposit + arbitrary call
Permit2 / EIP-2612 plumbingCustom interaction patterns
The two compose. Most apps run TradingClient for trading and execute for everything else.