Skip to main content
@chipi-stack/core is in development (v0.1.0). APIs may change before stable release.

Overview

TxBuilder provides a fluent API for composing multiple StarkNet calls into a single atomic transaction. All calls execute together via Account.execute(), which is more gas-efficient than separate transactions (important when sponsoring gas via a paymaster).

Usage

import { TxBuilder } from "@chipi-stack/core";
import type { Account } from "starknet";

const result = await new TxBuilder(account)
  .approve(tokenAddr, spenderAddr, amount)
  .transfer(tokenAddr, [{ to: recipient, amount: 1_000_000n }])
  .send();

console.log(result.transaction_hash);

Constructor

new TxBuilder(account: Account, opts?: { paymaster?: PaymasterAdapter })

Parameters

  • account (Account): A starknet.js Account instance
  • opts.paymaster (PaymasterAdapter, optional): Adapter for gasless transactions via sendSponsored()

Methods

add(call)

Add one or more raw StarkNet calls to the batch.
builder.add({
  contractAddress: "0x...",
  entrypoint: "swap",
  calldata: [tokenIn, tokenOut, amountIn, "0"],
});

// Or add multiple at once
builder.add([call1, call2, call3]);

Parameters

  • call (Call | Call[]): A single Call or array of Calls

Returns

this (chainable)

approve(token, spender, amount)

Add an ERC20 approve call. Handles uint256 encoding automatically.
builder.approve(USDC_ADDRESS, DEX_ADDRESS, 1_000_000n);

Parameters

  • token (string): Token contract address
  • spender (string): Address to approve
  • amount (bigint): Raw amount in base units

Returns

this (chainable)

transfer(token, targets)

Add one or more ERC20 transfer calls.
builder.transfer(USDC_ADDRESS, [
  { to: "0xalice...", amount: 500_000n },
  { to: "0xbob...", amount: 300_000n },
]);

Parameters

  • token (string): Token contract address
  • targets (TransferTarget[]): Array of { to: string, amount: bigint } pairs

Returns

this (chainable)

calls()

Returns a copy of the current call batch (for inspection).
const currentCalls = builder.calls();
console.log(`${currentCalls.length} calls queued`);

Returns

Call[]

send()

Execute all batched calls atomically.
const result = await builder.send();
console.log(result.transaction_hash);

Returns

Promise<InvokeFunctionResponse>

Throws

  • If no calls have been added
  • If send() was already called (builders are single-use)

sendSponsored()

Execute via Chipi’s paymaster (gasless). Requires a PaymasterAdapter in the constructor.
const txHash = await builder.sendSponsored();
console.log(txHash); // "0x..."

Returns

Promise<string> (transaction hash)

Throws

  • If no paymaster was configured in the constructor
  • If no calls have been added
  • If send() or sendSponsored() was already called (builders are single-use)

estimateFee()

Estimate the fee for the current batch without executing.
const fee = await builder.estimateFee();
console.log(`Estimated fee: ${fee.overall_fee} wei`);

Returns

Promise<EstimateFeeResponse>

Throws

  • If no calls have been added

preflight()

Simulate the transaction without submitting. Returns the transaction trace and fee estimation. Useful for checking if a transaction would succeed before sending.
const sim = await builder.preflight();
console.log(`Would cost: ${sim.fee_estimation.overall_fee} wei`);

Returns

Promise<PreflightResult> with transaction_trace and fee_estimation

Throws

  • If no calls have been added

Examples

Simple Transfer

const result = await new TxBuilder(account)
  .transfer(USDC_ADDRESS, [{ to: recipient, amount: 1_500_000n }])
  .send();

Approve + Swap (Atomic Batch)

const result = await new TxBuilder(account)
  .approve(USDC_ADDRESS, DEX_ADDRESS, 1_000_000n)
  .add({
    contractAddress: DEX_ADDRESS,
    entrypoint: "swap",
    calldata: [USDC_ADDRESS, ETH_ADDRESS, "1000000", "0"],
  })
  .send();

Fee Preview Before Sending

const builder = new TxBuilder(account)
  .transfer(USDC_ADDRESS, [{ to, amount }]);

const fee = await builder.estimateFee();
console.log(`This will cost: ${fee.overall_fee} wei`);

// User confirms, create new builder to send
const result = await new TxBuilder(account)
  .transfer(USDC_ADDRESS, [{ to, amount }])
  .send();

With Erc20 and Amount

import { TxBuilder, Erc20, Amount, TokenRegistry } from "@chipi-stack/core";

const registry = new TokenRegistry("mainnet");
const usdcInfo = registry.bySymbol("USDC")!;
const usdc = new Erc20(usdcInfo, account);
const amount = Amount.parse("50.0", usdcInfo);

const result = await new TxBuilder(account)
  .add(usdc.approveCall(spender, amount))
  .add(usdc.transferCall(recipient, amount))
  .send();

Gasless with Argent X / Braavos (browser)

import { connect } from "starknetkit";
import { TxBuilder } from "@chipi-stack/core";
import { ChipiBrowserSDK } from "@chipi-stack/backend";

const { wallet } = await connect();
const sdk = new ChipiBrowserSDK({ apiPublicKey: "pk_..." });
const paymaster = sdk.createPaymasterAdapter(userBearerToken);

// wallet.account already has the Argent/Braavos signer — triggers popup on sign
const txHash = await new TxBuilder(wallet.account, { paymaster })
  .transfer(USDC, [{ to: recipient, amount: 1_000_000n }])
  .sendSponsored(); // popup -> sign -> gasless execution

Gasless with Cartridge Controller

import Controller from "@cartridge/controller";
import { TxBuilder } from "@chipi-stack/core";
import { ChipiBrowserSDK } from "@chipi-stack/backend";

const controller = new Controller();
await controller.connect();

const sdk = new ChipiBrowserSDK({ apiPublicKey: "pk_..." });
const paymaster = sdk.createPaymasterAdapter(userBearerToken);

// controller IS Account-compatible
const txHash = await new TxBuilder(controller, { paymaster })
  .transfer(USDC, [{ to, amount }])
  .sendSponsored();

Backend Automation (gasless)

import { createAccount, DirectSigner, TxBuilder } from "@chipi-stack/core";
import { ChipiServerSDK } from "@chipi-stack/backend";

const sdk = new ChipiServerSDK({ apiPublicKey: "pk_...", apiSecretKey: "sk_..." });
const paymaster = sdk.createPaymasterAdapter();

const provider = { nodeUrl: "https://starknet-mainnet.infura.io/v3/YOUR_KEY" };
const accountAddress = "0xYOUR_ACCOUNT_ADDRESS";
const signer = new DirectSigner(process.env.AUTOMATION_KEY!, accountAddress);
const account = await createAccount({ signer, provider });

const txHash = await new TxBuilder(account, { paymaster })
  .approve(USDC, spender, amount)
  .transfer(USDC, [{ to, amount }])
  .sendSponsored(); // gasless, no popup
  • Amount: Type-safe token amounts for use with TxBuilder
  • Erc20: Generate typed Call objects for approve/transfer
  • SignerAdapter: Different signing strategies for the Account