InviaInvia
App
SDK/Creating offers

Creating offers

Posting an offer locks the maker's leg of the trade into a program-owned vault. Sell offers escrow tokens; buy offers escrow payment. The SDK does every PDA + ATA derivation for you and picks the optimal price scale.

Build the instruction

import { Connection, PublicKey, Transaction } from "@solana/web3.js";
import {
  InviaClient,
  PAYMENT_MINTS,
  parseTokenAmount,
  pickPriceAndScale,
} from "@invia-app/sdk";

const invia = new InviaClient({
  connection: new Connection("https://api.devnet.solana.com", "confirmed"),
});

const tokenMint = new PublicKey("Es9...");
const tokenDecimals = 6;
const paymentMint = PAYMENT_MINTS.WSOL;
const paymentDecimals = 9;

const sizeRaw = parseTokenAmount("500000", tokenDecimals);
const totalRaw = parseTokenAmount("5", paymentDecimals);
const priced = pickPriceAndScale(sizeRaw, totalRaw);
if (!priced) throw new Error("Total or size out of expressible range");

const built = invia.program.buildCreateOfferIx({
  maker: new PublicKey(wallet.address),
  side: "sell",
  tokenMint,
  paymentMint,
  size: sizeRaw,
  pricePerToken: priced.pricePerToken,
  priceScale: priced.priceScale,
  minFill: parseTokenAmount("50000", tokenDecimals),
  expiresAt: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
});

built.ix is a TransactionInstruction, built.pda is the offer account that will be created, and built.nonce is the random u64 used to seed the PDA.

Why size + total instead of price per token

The on-chain offer stores an integer price_per_token together with a price_scale. The total payment for a fill is size × price_per_token / 10^scale. pickPriceAndScale walks scales 0 → 18 and returns the smallest scale that lets the math represent your chosen total exactly, so the maker just types human numbers.

Sanity check

Pre-flight your transaction with connection.simulateTransaction against the same RPC you intend to send on. A passing simulation is a strong guarantee the fill will land.

Send it

const { signature, offer, nonce } = await invia.program.createOffer(wallet, {
  side: "sell",
  tokenMint,
  paymentMint,
  size: sizeRaw,
  pricePerToken: priced.pricePerToken,
  priceScale: priced.priceScale,
  minFill: parseTokenAmount("50000", tokenDecimals),
  expiresAt: BigInt(Math.floor(Date.now() / 1000) + 7 * 24 * 3600),
});

createOffer builds the instruction, fetches a recent blockhash, asks the wallet to sign, submits, and waits for the cluster to confirm.

Buy-side offers

await invia.program.createOffer(wallet, {
  side: "buy",
  tokenMint,
  paymentMint,
  size: parseTokenAmount("1000", tokenDecimals),
  pricePerToken: priced.pricePerToken,
  priceScale: priced.priceScale,
  minFill: parseTokenAmount("100", tokenDecimals),
  expiresAt: BigInt(Math.floor(Date.now() / 1000) + 24 * 3600),
});

For a buy offer, the maker funds the payment vault with size × price_per_token / 10^scale of the payment mint, and receives the token from takers as fills come in.

Constraints