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.
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
sizemust be a positivebigintin raw token unitspricePerTokenmust be a positivebigintpriceScalemust be an integer in[0, 18]minFillmust be>= 1and<= sizeexpiresAtmust be in the future and within 30 daystokenMintmust differ frompaymentMintpaymentMintmust be USDC mainnet, USDT mainnet, wSOL, or USDC devnet