Skip to content

Latest commit

 

History

History
520 lines (403 loc) · 13.9 KB

for-payments.mdx

File metadata and controls

520 lines (403 loc) · 13.9 KB
title Toolkit for Stablecoin Payments
sidebarTitle Integration Guide
description Guide to integrate light-token APIs for stablecoin payments with comparison to SPL.
keywords
stablecoin payments for enterprises
infrastructure for stablecoin payments on solana
payment rails for stablecoins on solana
usdc payments for merchants on solana
payment infrastructure for fintechs
micropayments for apps on solana
token extensions for payment companies
rent free tokens for payment apps

import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; import ToolkitsSetup from "/snippets/setup/toolkits-setup.mdx"; import RegisterSplMint from "/snippets/setup/register-spl-mint.mdx"; import AgentSkillPayments from "/snippets/setup/agent-skill-payments.mdx"; import PaymentsAiPrompt from "/snippets/ai-prompts/toolkits/payments.mdx"; import SupportFooter from "/snippets/support-footer.mdx";

  1. The light-token API matches the SPL-token API almost entirely, and extends their functionality to include the light token program in addition to the SPL-token and Token-2022 programs.
  2. Your users use the same stablecoins, just stored more efficiently.
Creation Cost SPL light-token
Token Account ~2,000,000 lamports ~11,000 lamports

What you will implement

SPL Light
**Receive** getOrCreateAssociatedTokenAccount() createLoadAtaInstructions()
**Transfer** createTransferInstruction() createTransferInterfaceInstructions()
**Get Balance** getAccount() getAtaInterface()
**Tx History** getSignaturesForAddress() rpc.getSignaturesForOwnerInterface()
**Wrap from SPL** N/A createWrapInstruction()
**Unwrap to SPL** N/A createUnwrapInstructions() / unwrap()
Find full runnable code examples [here](https://github.com/Lightprotocol/examples-light-token/tree/main/toolkits/payments-and-wallets).

Setup

Snippets below assume rpc, payer, mint, owner, recipient, and amount are defined. See the full examples for runnable setup.

import { createRpc } from "@lightprotocol/stateless.js";

import {
  createLoadAtaInstructions,
  loadAta,
  createTransferInterfaceInstructions,
  transferInterface,
  createUnwrapInstructions,
  unwrap,
  getAssociatedTokenAddressInterface,
  getAtaInterface,
  wrap,
} from "@lightprotocol/compressed-token/unified";

const rpc = createRpc(RPC_ENDPOINT);

Receive Payments

Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/receive.ts).

Load creates the associated token account (ATA) if needed and loads any compressed state into it. Share the ATA address with the sender.

**About loading**: Light Token accounts reduce account rent ~200x by auto-compressing inactive accounts. Before any action, the SDK detects cold balances and adds instructions to load them. This almost always fits in a single atomic transaction with your regular transfer. APIs return `TransactionInstruction[][]` so the same loop handles the rare multi-transaction case automatically.
import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
import {
  createLoadAtaInstructions,
  getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";

const ata = getAssociatedTokenAddressInterface(mint, recipient);

// Returns TransactionInstruction[][].
// Each inner array is one transaction.
// Almost always returns just one.
const instructions = await createLoadAtaInstructions(
  rpc,
  ata,
  recipient,
  mint,
  payer.publicKey
);

for (const ixs of instructions) {
  const tx = new Transaction().add(...ixs);
  await sendAndConfirmTransaction(rpc, tx, [payer]);
}
import {
  loadAta,
  getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";

const ata = getAssociatedTokenAddressInterface(mint, recipient);

const sig = await loadAta(rpc, ata, recipient, mint, payer);
if (sig) console.log("Loaded:", sig);
import {
  createAssociatedTokenAccountInstruction,
  getAssociatedTokenAddressSync,
  getOrCreateAssociatedTokenAccount,
} from "@solana/spl-token";

const ata = getAssociatedTokenAddressSync(mint, recipient);

// Instruction:
const tx = new Transaction().add(
  createAssociatedTokenAccountInstruction(payer.publicKey, ata, recipient, mint)
);

// Action:
const ata = await getOrCreateAssociatedTokenAccount(
  connection, payer, mint, recipient
);

Send Payments

Find a full code example: [instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/send-instruction.ts) | [action](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/send-action.ts).
import { Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
import {
  createTransferInterfaceInstructions,
  getOrCreateAtaInterface,
  getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";

await getOrCreateAtaInterface(rpc, payer, mint, recipient);
const destination = getAssociatedTokenAddressInterface(mint, recipient);

const instructions = await createTransferInterfaceInstructions(
  rpc,
  payer.publicKey,
  mint,
  amount,
  owner.publicKey,
  destination
);

for (const ixs of instructions) {
  const tx = new Transaction().add(...ixs);
  await sendAndConfirmTransaction(rpc, tx, [payer, owner]);
}

Your app logic may require you to create a single sign request for your user. Here's how to do this:

const transactions = instructions.map((ixs) => new Transaction().add(...ixs));

// One approval for all
const signed = await wallet.signAllTransactions(transactions);

for (const tx of signed) {
  // send...
  await sendAndConfirmTransaction(rpc, tx);
}

While almost always you will have only one transfer transaction, you can optimize sending in the rare cases where you have multiple transactions. parallelize the loads, confirm them, and then send the transfer instruction after.

import {
  createTransferInterfaceInstructions,
  getOrCreateAtaInterface,
  getAssociatedTokenAddressInterface,
  sliceLast,
} from "@lightprotocol/compressed-token/unified";

await getOrCreateAtaInterface(rpc, payer, mint, recipient);
const destination = getAssociatedTokenAddressInterface(mint, recipient);

const instructions = await createTransferInterfaceInstructions(
  rpc,
  payer.publicKey,
  mint,
  amount,
  owner.publicKey,
  destination
);
const { rest: loadInstructions, last: transferInstructions } = sliceLast(instructions);
// empty = nothing to load, will no-op.
await Promise.all(
  loadInstructions.map((ixs) => {
    const tx = new Transaction().add(...ixs);
    tx.sign(payer, owner);
    return sendAndConfirmTransaction(rpc, tx);
  })
);

const transferTx = new Transaction().add(...transferInstructions);
transferTx.sign(payer, owner);
await sendAndConfirmTransaction(rpc, transferTx);
import {
  getAssociatedTokenAddressSync,
  createAssociatedTokenAccountIdempotentInstruction,
  createTransferInstruction,
} from "@solana/spl-token";

const sourceAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
const destinationAta = getAssociatedTokenAddressSync(mint, recipient);

const tx = new Transaction().add(
  createAssociatedTokenAccountIdempotentInstruction(
    payer.publicKey, destinationAta, recipient, mint
  ),
  createTransferInstruction(sourceAta, destinationAta, owner.publicKey, amount)
);
import {
  getAssociatedTokenAddressInterface,
  getOrCreateAtaInterface,
  transferInterface,
} from "@lightprotocol/compressed-token/unified";

const sourceAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);
await getOrCreateAtaInterface(rpc, payer, mint, recipient);
const destination = getAssociatedTokenAddressInterface(mint, recipient);

await transferInterface(rpc, payer, sourceAta, mint, destination, owner, amount);
import {
  getAssociatedTokenAddressSync,
  getOrCreateAssociatedTokenAccount,
  transfer,
} from "@solana/spl-token";

await getOrCreateAssociatedTokenAccount(connection, payer, mint, recipient);

const sourceAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
const destinationAta = getAssociatedTokenAddressSync(mint, recipient);
await transfer(connection, payer, sourceAta, destinationAta, owner, amount);

Show Balance

Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/get-balance.ts).
import {
  getAssociatedTokenAddressInterface,
  getAtaInterface,
} from "@lightprotocol/compressed-token/unified";

const ata = getAssociatedTokenAddressInterface(mint, owner);
const account = await getAtaInterface(rpc, ata, owner, mint);

console.log(account.parsed.amount);
import { getAccount } from "@solana/spl-token";

const account = await getAccount(connection, ata);

console.log(account.amount);

Transaction History

Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/get-history.ts).
const result = await rpc.getSignaturesForOwnerInterface(owner);

console.log(result.signatures); // Merged + deduplicated
console.log(result.solana); // On-chain txs only
console.log(result.compressed); // Compressed txs only

Use getSignaturesForAddressInterface(address) if you want address-specific rather than owner-wide history.

const signatures = await connection.getSignaturesForAddress(ata);

Wrap from SPL

Wrap tokens from SPL/Token-2022 accounts to light-token ATA.

Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/wrap.ts).
import { Transaction } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import {
  createWrapInstruction,
  getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";
import { getSplInterfaceInfos } from "@lightprotocol/compressed-token";

const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

const splInterfaceInfos = await getSplInterfaceInfos(rpc, mint);
const splInterfaceInfo = splInterfaceInfos.find((i) => i.isInitialized);

const tx = new Transaction().add(
  createWrapInstruction(
    splAta,
    tokenAta,
    owner.publicKey,
    mint,
    amount,
    splInterfaceInfo,
    decimals,
    payer.publicKey
  )
);
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import {
  wrap,
  getAssociatedTokenAddressInterface,
} from "@lightprotocol/compressed-token/unified";

const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);
const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);

await wrap(rpc, payer, splAta, tokenAta, owner, mint, amount);

Unwrap to SPL

Unwrap moves the token balance from a light-token account to a SPL-token account. Use this to compose with applications that do not yet support light-token.

Find a full code example [here](https://github.com/Lightprotocol/examples-light-token/blob/main/toolkits/payments-and-wallets/unwrap.ts).
import { Transaction } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { createUnwrapInstructions } from "@lightprotocol/compressed-token/unified";

const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);

// Each inner array = one transaction. Handles loading + unwrapping together.
const instructions = await createUnwrapInstructions(
  rpc,
  splAta,
  owner.publicKey,
  mint,
  amount,
  payer.publicKey
);

for (const ixs of instructions) {
  const tx = new Transaction().add(...ixs);
  await sendAndConfirmTransaction(rpc, tx, [payer, owner]);
}
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { unwrap } from "@lightprotocol/compressed-token/unified";

const splAta = getAssociatedTokenAddressSync(mint, owner.publicKey);

await unwrap(rpc, payer, splAta, owner, mint, amount);

Sponsor Rent-Top Ups for Users

Abstract away SOL costs by sponsoring rent top-ups on behalf of your users.