Loans
Purchase
Approve-and-purchase calldata that pays the downpayment and starts the loan
POST
/
seller-financing
/
api
/
markets
/
{marketId}
/
listings
/
{listingId}
/
purchase
curl -X POST "https://api.trusset.org/seller-financing/api/markets/cmsg4x7k20003sf9q1d8v6b2e/listings/4/purchase" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"from": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8"}'
const res = await fetch(
`https://api.trusset.org/seller-financing/api/markets/${marketId}/listings/${listingId}/purchase`,
{
method: 'POST',
headers: { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' },
body: JSON.stringify({ from: await buyer.getAddress() })
}
);
const { data } = await res.json();
for (const step of data.steps) {
const tx = await buyer.sendTransaction({ to: step.to, data: step.data, value: 0, gasLimit: step.gasLimit });
await tx.wait();
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"chainId": 11155111,
"steps": [
{
"to": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"data": "0x095ea7b3...",
"value": "0",
"functionName": "approve",
"label": "Approve exactly the downpayment of 2000.0",
"description": "Approve exactly the downpayment of 2000.0",
"chainId": 11155111
},
{
"to": "0xc35e5f74eC4C1dfB8cE2b8798D6161B706F9B5E1",
"data": "0xefef39a1...",
"value": "0",
"functionName": "purchase",
"label": "Buy listing 4 with a downpayment of 2000.0",
"description": "Buy listing 4 with a downpayment of 2000.0",
"gasLimit": "579315",
"chainId": 11155111
}
],
"confirmStepIndex": 1,
"preflight": {
"simulated": true,
"basis": "approvals-applied",
"gasEstimate": "386210",
"note": "Simulated with the approvals above applied first."
},
"downpayment": "2000000000",
"downpaymentFormatted": "2000.0",
"principal": "8000000000",
"principalFormatted": "8000.0"
},
"error": null,
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "This listing is reserved for another buyer.",
"details": {
"action": "purchase",
"error": "NotDesignatedBuyer",
"args": [],
"revertData": "0x49042af3"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "Listing 6 expired at 2026-10-18T12:00:00.000Z, so it can no longer be bought. Only the seller or the operator can cancel it.",
"details": {
"action": "purchase",
"error": "ListingExpired",
"expiresAt": 1792324800
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "The wallet 0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8 holds 1250000000 of the token, 2000000000 are needed.",
"details": {
"token": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"owner": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8",
"balance": "1250000000",
"needed": "2000000000"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
Seller Financing is in beta and runs on Ethereum Sepolia instances only for now (
DEVELOPMENT instances on Ethereum). A PRODUCTION instance, or one on another network, answers 503 SELLER_FINANCING_UNAVAILABLE. See Seller Financing.OPEN listing for the buyer to sign: an exact approval of the downpayment on the payment token, then purchase. The market pulls exactly the downpayment and holds it in escrow. The loan starts at the block time of the purchase, and its interest periods count from there.
In freeze mode, the purchase freezes the listed amount in the seller’s wallet. In custody mode the asset is already in the adapter, and nothing moves. Either way, the asset reaches the buyer only after full payment, through Claim Asset.
The market admits a buyer that:
- is not the seller, the market or its adapter,
- is the designated buyer, when the listing names one,
- is verified on the market identity register, when the market names one,
- is not refused by the asset token’s own identity register, because the token would otherwise refuse the delivery.
expiresAt with 409 TX_WOULD_REVERT, error ListingExpired and details.expiresAt. List Listings with status=OPEN already leaves such listings out. In freeze mode it also checks that the freeze adapter still holds its agent role and that the seller still holds the listed amount unfrozen. Either failure answers 409 TX_WOULD_REVERT, with AgentRoleMissing or InsufficientFreeBalance.
Path Parameters
string
required
The market’s record ID, as
id on List Markets.string
required
The listing’s ID on the market.
Body Parameters
string
required
The buyer’s wallet, which signs both steps. It must hold the downpayment in the payment token. Without it the answer is
400 FROM_REQUIRED.Response Fields
object
Show child attributes
Show child attributes
string
SIGN_TRANSACTIONS.integer
The chain the steps are built for.
array
Any reset of an existing allowance to zero, the approval of exactly the downpayment, then
purchase. With an allowance that already covers the downpayment, purchase alone, with gasLimit.integer
Index of the
purchase step.object
{ simulated: true, gasEstimate } when no approval is needed. With an approval step, { simulated: true, basis: "approvals-applied", gasEstimate, note } when the approval and the purchase were simulated together, and the purchase step then carries gasLimit. { simulated: false, note } when that bundle could not be simulated: the market’s own checks of the buyer and the listing passed, and the wallet estimates gas once the approval is mined. See Writes.string
The downpayment, in the payment token’s smallest unit.
string
The same in whole units.
string
What the buyer owes after the purchase, before interest, in smallest units.
string
The same in whole units.
The approval is exact, never unlimited. A wallet already holding a smaller non-zero allowance gets a step that resets it to zero first, because some tokens refuse to change a non-zero allowance.
curl -X POST "https://api.trusset.org/seller-financing/api/markets/cmsg4x7k20003sf9q1d8v6b2e/listings/4/purchase" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"from": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8"}'
const res = await fetch(
`https://api.trusset.org/seller-financing/api/markets/${marketId}/listings/${listingId}/purchase`,
{
method: 'POST',
headers: { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' },
body: JSON.stringify({ from: await buyer.getAddress() })
}
);
const { data } = await res.json();
for (const step of data.steps) {
const tx = await buyer.sendTransaction({ to: step.to, data: step.data, value: 0, gasLimit: step.gasLimit });
await tx.wait();
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"chainId": 11155111,
"steps": [
{
"to": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"data": "0x095ea7b3...",
"value": "0",
"functionName": "approve",
"label": "Approve exactly the downpayment of 2000.0",
"description": "Approve exactly the downpayment of 2000.0",
"chainId": 11155111
},
{
"to": "0xc35e5f74eC4C1dfB8cE2b8798D6161B706F9B5E1",
"data": "0xefef39a1...",
"value": "0",
"functionName": "purchase",
"label": "Buy listing 4 with a downpayment of 2000.0",
"description": "Buy listing 4 with a downpayment of 2000.0",
"gasLimit": "579315",
"chainId": 11155111
}
],
"confirmStepIndex": 1,
"preflight": {
"simulated": true,
"basis": "approvals-applied",
"gasEstimate": "386210",
"note": "Simulated with the approvals above applied first."
},
"downpayment": "2000000000",
"downpaymentFormatted": "2000.0",
"principal": "8000000000",
"principalFormatted": "8000.0"
},
"error": null,
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "This listing is reserved for another buyer.",
"details": {
"action": "purchase",
"error": "NotDesignatedBuyer",
"args": [],
"revertData": "0x49042af3"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "Listing 6 expired at 2026-10-18T12:00:00.000Z, so it can no longer be bought. Only the seller or the operator can cancel it.",
"details": {
"action": "purchase",
"error": "ListingExpired",
"expiresAt": 1792324800
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "The wallet 0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8 holds 1250000000 of the token, 2000000000 are needed.",
"details": {
"token": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"owner": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8",
"balance": "1250000000",
"needed": "2000000000"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
Error Codes
| Code | HTTP | Cause |
|---|---|---|
INVALID_LISTING_ID | 400 | listingId is not a positive integer of up to 19 digits |
VALIDATION_ERROR | 400 | from is malformed, or the body carries another field. error.details names it |
FROM_REQUIRED | 400 | from is missing. The message names who signs: the buyer wallet that pays the downpayment |
MARKET_NOT_FOUND | 404 | No market with this ID that your instance reaches on its network |
LISTING_NOT_FOUND | 404 | No listing with this ID on the market |
INVALID_STATUS | 409 | The listing is not OPEN. details.status names its status |
INSUFFICIENT_BALANCE | 409 | from holds less of the payment token than the downpayment. details carries token, owner, balance and needed |
TX_WOULD_REVERT | 409 | The purchase would revert. details.error names the reason, for example ListingExpired with details.expiresAt, NotDesignatedBuyer, InvalidBuyer, IdentityNotVerified, BuyerNotVerified, SellerIsTokenAgent, EnforcedPause, or in freeze mode AgentRoleMissing and InsufficientFreeBalance |
CHAIN_UNAVAILABLE | 503 | The chain could not be read. Nothing was built. Retry shortly |
SELLER_FINANCING_UNAVAILABLE | 503 | The instance is a PRODUCTION instance or not on Ethereum |
PURCHASE_BUILD_FAILED | 500 | Unexpected failure. Nothing was built. Retry, or contact support with the request ID |
⌘I
curl -X POST "https://api.trusset.org/seller-financing/api/markets/cmsg4x7k20003sf9q1d8v6b2e/listings/4/purchase" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"from": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8"}'
const res = await fetch(
`https://api.trusset.org/seller-financing/api/markets/${marketId}/listings/${listingId}/purchase`,
{
method: 'POST',
headers: { 'X-API-Key': 'trusset_your_key_here', 'Content-Type': 'application/json' },
body: JSON.stringify({ from: await buyer.getAddress() })
}
);
const { data } = await res.json();
for (const step of data.steps) {
const tx = await buyer.sendTransaction({ to: step.to, data: step.data, value: 0, gasLimit: step.gasLimit });
await tx.wait();
}
{
"success": true,
"data": {
"action": "SIGN_TRANSACTIONS",
"chainId": 11155111,
"steps": [
{
"to": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"data": "0x095ea7b3...",
"value": "0",
"functionName": "approve",
"label": "Approve exactly the downpayment of 2000.0",
"description": "Approve exactly the downpayment of 2000.0",
"chainId": 11155111
},
{
"to": "0xc35e5f74eC4C1dfB8cE2b8798D6161B706F9B5E1",
"data": "0xefef39a1...",
"value": "0",
"functionName": "purchase",
"label": "Buy listing 4 with a downpayment of 2000.0",
"description": "Buy listing 4 with a downpayment of 2000.0",
"gasLimit": "579315",
"chainId": 11155111
}
],
"confirmStepIndex": 1,
"preflight": {
"simulated": true,
"basis": "approvals-applied",
"gasEstimate": "386210",
"note": "Simulated with the approvals above applied first."
},
"downpayment": "2000000000",
"downpaymentFormatted": "2000.0",
"principal": "8000000000",
"principalFormatted": "8000.0"
},
"error": null,
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "This listing is reserved for another buyer.",
"details": {
"action": "purchase",
"error": "NotDesignatedBuyer",
"args": [],
"revertData": "0x49042af3"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "TX_WOULD_REVERT",
"message": "Listing 6 expired at 2026-10-18T12:00:00.000Z, so it can no longer be bought. Only the seller or the operator can cancel it.",
"details": {
"action": "purchase",
"error": "ListingExpired",
"expiresAt": 1792324800
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
{
"success": false,
"data": null,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "The wallet 0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8 holds 1250000000 of the token, 2000000000 are needed.",
"details": {
"token": "0x98aD0ca091552e23C564B41C74282e5343D03E8a",
"owner": "0x09BD3f5c18488e492a7bC3C7CCfc6cc837f511e8",
"balance": "1250000000",
"needed": "2000000000"
}
},
"metadata": {
"timestamp": "2026-10-19T08:58:41.507Z",
"requestId": "3f7c2a91-6d84-4b0e-9a15-c2e8f4d7b603",
"instanceId": "inst_abc123"
}
}
