SEP-24 defines a standard for hosted deposit and withdrawal flows where anchors provide web interfaces for users to complete transactions. PayOnProof integrates with SEP-24 anchors to offer seamless on-ramp and off-ramp experiences.
Overview
SEP-24 enables anchors to host their own KYC, payment collection, and verification interfaces while maintaining a standardized integration pattern. This is the most common implementation for user-facing flows.
Key characteristics
Hosted UI : Anchors control the user interface and experience
Web-based : Users complete flows in web browsers or webviews
KYC compliant : Anchors handle regulatory requirements
SEP-10 authentication : Requires authenticated sessions
Fetching SEP-24 capabilities
PayOnProof discovers SEP-24 endpoints through the anchor’s stellar.toml file and fetches capability information from the /info endpoint.
Info endpoint integration
services/api/lib/stellar/sep24.ts
export async function fetchSep24Info ( input : Sep24InfoInput ) : Promise <{
infoUrl : string ;
transferServerSep24 : string ;
domain ?: string ;
info : unknown ;
}> {
let transferServer = input . transferServerSep24 ?. trim ();
let domain = input . domain ?. trim ();
if ( ! transferServer ) {
if ( ! domain ) {
throw new Error (
"Either transferServerSep24 or domain is required to fetch SEP-24 info"
);
}
const discovered = await discoverAnchorFromDomain ({ domain });
transferServer = discovered . transferServerSep24 ;
domain = discovered . domain ;
}
if ( ! transferServer ) {
throw new Error ( "TRANSFER_SERVER_SEP0024 not found in stellar.toml" );
}
const transferServerSep24 = normalizeBaseUrl ( transferServer );
const infoUrl = ` ${ transferServerSep24 } /info` ;
const response = await fetchWithTimeout (
infoUrl ,
input . timeoutMs ?? DEFAULT_TIMEOUT_MS
);
if ( ! response . ok ) {
throw new Error (
`Failed to load SEP-24 /info ( ${ response . status } ${ response . statusText } )`
);
}
const info = await response . json ();
return { infoUrl , transferServerSep24 , domain , info };
}
The implementation is in services/api/lib/stellar/sep24.ts:29
Discovery flow
The SEP-24 discovery process follows these steps:
Domain resolution : If only a domain is provided, fetch the stellar.toml file
Endpoint extraction : Extract TRANSFER_SERVER_SEP0024 from the TOML
Info fetch : Query the /info endpoint for supported operations
Capability parsing : Extract deposit/withdraw support and fee information
Info endpoint response
The /info endpoint returns details about supported assets and operations:
{
"deposit" : {
"USDC" : {
"enabled" : true ,
"fee_fixed" : 0.1 ,
"fee_percent" : 0.1 ,
"min_amount" : 10 ,
"max_amount" : 10000 ,
"fields" : {
"email_address" : {
"description" : "Email address for transaction notifications" ,
"optional" : true
}
}
}
},
"withdraw" : {
"USDC" : {
"enabled" : true ,
"fee_fixed" : 0.1 ,
"fee_percent" : 0.1 ,
"types" : {
"bank_account" : {
"fields" : {
"dest" : { "description" : "Bank account number" },
"dest_extra" : { "description" : "Routing number" }
}
}
}
}
},
"fee" : {
"enabled" : true
},
"transactions" : {
"enabled" : true
},
"transaction" : {
"enabled" : true
}
}
PayOnProof extracts fee information from the SEP-24 info response to display costs to users:
services/api/lib/stellar/capabilities.ts
function extractFeeFromInfo (
info : unknown ,
assetCode : string
) : { fixed ?: number ; percent ?: number } | undefined {
if ( ! info || typeof info !== "object" ) return undefined ;
const root = info as Record < string , unknown >;
const deposit = root . deposit as Record < string , unknown > | undefined ;
const withdraw = root . withdraw as Record < string , unknown > | undefined ;
const candidates = [
deposit ?.[ assetCode ],
withdraw ?.[ assetCode ],
deposit ?.[ ` ${ assetCode } :*` ],
withdraw ?.[ ` ${ assetCode } :*` ],
];
const found = candidates . find (( v ) => v && typeof v === "object" ) as
| Record < string , unknown >
| undefined ;
if ( ! found ) return undefined ;
return {
fixed: toNumber ( found . fee_fixed ),
percent: toNumber ( found . fee_percent ),
};
}
Fees can be specified as fixed amounts (fee_fixed) or percentages (fee_percent), or both. PayOnProof extracts both types when available.
Timeout configuration
All SEP-24 requests support configurable timeouts:
const DEFAULT_TIMEOUT_MS = 8000 ;
async function fetchWithTimeout ( url : string , timeoutMs : number ) : Promise < Response > {
const controller = new AbortController ();
const timer = setTimeout (() => controller . abort (), timeoutMs );
try {
return await fetch ( url , {
method: "GET" ,
headers: { Accept: "application/json" },
signal: controller . signal ,
});
} finally {
clearTimeout ( timer );
}
}
The default timeout is 8 seconds, but can be customized via the timeoutMs parameter.
Integration with anchor discovery
SEP-24 info is automatically fetched during the anchor discovery process:
services/api/lib/stellar/horizon.ts
if ( sep1 . transferServerSep24 ) {
try {
sep24Info = (
await fetchSep24Info ({
transferServerSep24: sep1 . transferServerSep24 ,
timeoutMs ,
})
). info ;
} catch {
// ignore - anchor may not support SEP-24
}
}
const extracted = [
... extractTypesAndCurrencies ( sep24Info ),
... extractTypesAndCurrencies ( sep6Info ),
];
The Horizon integration uses SEP-24 and SEP-6 info to automatically catalog which anchors support on-ramp and off-ramp for different currencies and countries.
Error handling
SEP-24 requests include comprehensive error handling:
if ( ! response . ok ) {
throw new Error (
`Failed to load SEP-24 /info ( ${ response . status } ${ response . statusText } )`
);
}
Errors include HTTP status codes and status text for debugging.
Next steps
SEP-10 authentication Required before initiating SEP-24 flows
SEP-31 direct payments Alternative for programmatic transfers
Capability resolution How PayOnProof determines anchor support
Anchor trust Security validation for anchors