Settlement tickets
Issue a signed, wallet-scoped settlement claim for a recipient on a supported network.
Create a ticket
amountMicros must be at least 10000, which represents 0.010000 of the settlement token.
curl https://starswap.cc/api/v1/tickets \
-X POST \
-H "Authorization: Bearer $STARSWAP_API_KEY" \
-H "Idempotency-Key: ticket-$(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"walletId":"wlt_example",
"chain":"base",
"amountMicros":"10000",
"recipient":"0xRecipient"
}'Read tickets
Use GET /v1/tickets?walletId=wlt_example to list a wallet's tickets. Read a single ticket with GET /v1/tickets/:ticketId.
Solana ticket signatures bind the vault program, config, network, mint, one-time nullifier, recipient account, raw amount, and expiry slot. The signed value is a domain-separated SHA-256 digest. The issuance slot remains offchain ticket metadata and is not encoded in the claim. Submit a claim only to the matching StarSwap vault.
Nullifiers use compact onchain bitmaps. Signed claims share 1,032-byte pages that each cover 8,192 claims. Deposits use 136-byte pages scoped to the signing depositor, with 1,024 deposits per page. Another wallet cannot consume your deposit bits.
The claim field is discriminated by kind. EVM tickets return evm_contract_call. Solana tickets return solana_program_instruction with the program ID, ordered account metas, compact claim instruction data, recoverable Secp256k1 verification data, and issued and expiry slots.
Submit a Solana claim
StarSwap returns the claim descriptor and never submits a public settlement claim. Any caller can provide a fresh blockhash and fee payer. Account 4 supplies the recipient, which the vault includes when it reconstructs the signed digest. The recipient does not sign and cannot be replaced. The caller funds recipient ATA creation when it is needed.
The 97-byte claim data embeds the compact signature and recovery ID. No signature-precompile instruction or instructions-sysvar account is needed. The following Node.js example uses @solana/kit to submit the single StarSwap claim instruction.
import {
AccountRole,
addSignersToTransactionMessage,
address,
appendTransactionMessageInstructions,
blockhash,
createSolanaRpc,
createTransactionMessage,
getBase64EncodedWireTransaction,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
type Instruction,
type TransactionSigner,
} from "@solana/kit";
type SolanaClaim = {
kind: "solana_program_instruction";
programId: string;
nullifier: string;
accounts: Array<
| { kind: "feePayer"; role: "writableSigner" }
| { kind: "address"; address: string; role: "readonly" | "writable" }
>;
instructionDataBase64: string;
verification: {
algorithm: "secp256k1_recover";
publicKey: string;
messageBase64: string;
signatureBase64: string;
};
};
export async function submitSolanaClaim(input: {
claim: SolanaClaim;
walletRpcUrl: string;
feePayer: TransactionSigner;
}) {
const rpc = createSolanaRpc(input.walletRpcUrl);
const lifetime = await rpc
.getLatestBlockhash({ commitment: "finalized" })
.send()
.then((response) => response.value);
if (input.claim.verification.algorithm !== "secp256k1_recover") {
throw new Error("Unsupported Solana claim signature algorithm");
}
const claim: Instruction = Object.freeze({
programAddress: address(input.claim.programId),
accounts: Object.freeze(input.claim.accounts.map((meta) =>
meta.kind === "feePayer"
? { address: input.feePayer.address, role: AccountRole.WRITABLE_SIGNER }
: {
address: address(meta.address),
role: meta.role === "writable" ? AccountRole.WRITABLE : AccountRole.READONLY,
},
)),
data: Uint8Array.from(Buffer.from(input.claim.instructionDataBase64, "base64")),
});
const message = appendTransactionMessageInstructions(
[claim],
setTransactionMessageLifetimeUsingBlockhash(
{
blockhash: blockhash(lifetime.blockhash),
lastValidBlockHeight: lifetime.lastValidBlockHeight,
},
setTransactionMessageFeePayerSigner(
input.feePayer,
createTransactionMessage({ version: 0 }),
),
),
);
const signed = await signTransactionMessageWithSigners(
addSignersToTransactionMessage([input.feePayer], message),
);
return rpc.sendTransaction(getBase64EncodedWireTransaction(signed), {
encoding: "base64",
maxRetries: 0n,
skipPreflight: true,
}).send();
}Read walletRpcUrl from the Solana descriptor returned by GET /v1/chains. Obtain feePayer from a Wallet Standard compatible signer or another @solana/kit signer. Fetch a new claim descriptor if its validUntil.value slot has passed. This release has no hosted claim-submission route.
