Borrowing
Get Permit Quote
The EIP-2612 permit a borrower signs to repay a loan in one transaction
POST
/
lending-external-securities-v2
/
api
/
positions
/
{marketId}
/
permit-quote
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/permit-quote" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "all": true}'
import { ethers } from 'ethers';
const base = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const quoteRes = await fetch(`${base}/permit-quote`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, all: true })
});
const { data: quote } = await quoteRes.json();
const { domain, types, message } = quote.typedData;
const signature = await borrowerWallet._signTypedData(domain, types, message);
const { v, r, s } = ethers.utils.splitSignature(signature);
const repayRes = await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({
...quote.repay,
permit: { value: quote.permit.value, deadline: quote.permit.deadline, v, r, s }
})
});
const { data: repayment } = await repayRes.json();
const hashes: string[] = [];
for (const step of repayment.steps) {
const tx = await borrowerWallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: hashes[repayment.confirmStepIndex] })
});
{
"success": true,
"data": {
"success": true,
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 11155111,
"verifyingContract": "0x98ad0ca091552e23c564b41c74282e5343d03e8a"
},
"types": {
"Permit": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
},
"primaryType": "Permit",
"message": {
"owner": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"spender": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"value": "50515706763",
"nonce": "0",
"deadline": 1790418600
}
},
"permit": {
"value": "50515.706763",
"deadline": 1790418600
},
"repay": {
"loanId": "5",
"all": true
},
"fullRepayment": true,
"outstandingDebt": "50412.881",
"borrowAssetSymbol": "USDC",
"borrowAssetAddress": "0x98ad0ca091552e23c564b41c74282e5343d03e8a",
"signer": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"nonce": "0",
"transactionFee": {
"kind": "REPAY",
"fee": "2.0",
"feeRaw": "2000000",
"message": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan."
},
"warning": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan.",
"description": "Sign this EIP-2612 permit from the borrower wallet 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 over 50515.706763 USDC (the outstanding debt plus a 20 bps accrual buffer and the repayment fee), then send the repayment with all:true and the permit; the market pulls exactly the true debt and the fee and the second step clears the leftover allowance"
}
}
{
"success": false,
"error": {
"code": "PERMIT_UNSUPPORTED",
"message": "The settlement asset USDT does not implement EIP-2612, so single-signature repayment is not available on this market. Use the approve-then-repay steps instead."
}
}
{
"success": false,
"error": {
"code": "PERMIT_DOMAIN_UNRESOLVED",
"message": "The EIP-712 domain of USDC could not be rebuilt from its name() and version(): no candidate reproduces the token's DOMAIN_SEPARATOR, so a permit quoted here could not be verified. Sign the permit with tooling that knows the token's domain, or use the approve-then-repay steps."
}
}
Returns the EIP-2612 permit the borrower signs to repay a loan through Repay Loan in a single transaction. A wallet cannot read the figures the permit has to carry, so the quote supplies all of them. That is the value, the token’s current nonce for the borrower, a bounded deadline and the token’s EIP-712 domain. The permit names the market as spender.
The quote exists only where the settlement asset provably implements EIP-2612, and only the loan’s borrower can use it. A third party repaying someone else’s loan uses the approve-then-repay steps on Repay Loan. Signing the permit moves nothing by itself: the repayment transaction redeems it.
A partial quote is exact: the repayment must carry the same amount. A full-repayment quote covers the debt at quote time plus the buffer, and interest keeps accruing. If the live debt plus the fee has outgrown the value by the time Repay Loan is called, it refuses with
Path Parameters
string
required
Body Parameters
integer
required
On-chain loan ID. A positive integer. Accepted as a JSON number or as a numeric string.
string
Amount to repay, as a positive decimal string in borrow asset units. The permit is quoted over exactly this amount. Required unless
all is true.boolean
Quote a full repayment. The value is the live debt plus a 20 basis point accrual buffer, plus the repayment fee on that figure.
integer
How long the permit stays valid, in seconds from now. An integer from
60 to 86400. Defaults to 1800.Sign and repay
1
Quote
Request the quote with the loan and either
amount or all: true.2
Sign
Sign
typedData from the wallet named in signer, which is the loan’s borrower, and split the signature into v, r and s.3
Build the repayment
Call Repay Loan with the fields in
repay, plus a permit holding permit.value, permit.deadline, v, r and s.4
Broadcast and confirm
Send the returned steps from the borrower’s wallet, then confirm with the hash of the step at
confirmStepIndex.INVALID_PERMIT. If the growth only eats into the buffer, the repayment builds with a warning.
The repay call also checks the signature against the borrower’s current nonce. If another permit from the borrower is redeemed on the token in between, the nonce moves and the repay call refuses this signature with INVALID_PERMIT. Request a fresh quote.
Domain resolution
A token does not expose its EIP-712 domain as a readable struct, so the API rebuilds it fromname() and version(). It tries the token’s own version() first, then 1 and 2, and accepts a candidate only when it reproduces the token’s DOMAIN_SEPARATOR. The wallet therefore signs a digest the token will recover.
When no candidate matches, the call returns PERMIT_DOMAIN_UNRESOLVED. Sign with tooling that knows the token’s domain, or use the approve-then-repay steps.
Response Fields
object
Show child attributes
Show child attributes
boolean
true, nested inside data.string
SIGN_TYPED_DATA.object
The EIP-712 payload to sign. Pass
domain, types and message straight to the wallet’s typed-data signer.Show child attributes
Show child attributes
object
value as a decimal string in borrow asset units, and deadline in unix seconds. Carry both into the permit of the repayment.object
boolean
Whether the quote covers the whole debt.
string
Principal plus accrued interest at quote time, without the repayment fee.
string
Symbol of the settlement asset.
string
Address of the settlement asset, the token the permit is signed for.
string
The loan’s borrower, the only wallet whose signature the repayment accepts.
string
The borrower’s current permit nonce on the token.
object
The repayment fee, present only when it is not zero. Same shape as on Repay Loan.
string
The fee message, present alongside
transactionFee.string
Plain-language instruction for the borrower.
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/permit-quote" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "all": true}'
import { ethers } from 'ethers';
const base = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const quoteRes = await fetch(`${base}/permit-quote`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, all: true })
});
const { data: quote } = await quoteRes.json();
const { domain, types, message } = quote.typedData;
const signature = await borrowerWallet._signTypedData(domain, types, message);
const { v, r, s } = ethers.utils.splitSignature(signature);
const repayRes = await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({
...quote.repay,
permit: { value: quote.permit.value, deadline: quote.permit.deadline, v, r, s }
})
});
const { data: repayment } = await repayRes.json();
const hashes: string[] = [];
for (const step of repayment.steps) {
const tx = await borrowerWallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: hashes[repayment.confirmStepIndex] })
});
{
"success": true,
"data": {
"success": true,
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 11155111,
"verifyingContract": "0x98ad0ca091552e23c564b41c74282e5343d03e8a"
},
"types": {
"Permit": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
},
"primaryType": "Permit",
"message": {
"owner": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"spender": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"value": "50515706763",
"nonce": "0",
"deadline": 1790418600
}
},
"permit": {
"value": "50515.706763",
"deadline": 1790418600
},
"repay": {
"loanId": "5",
"all": true
},
"fullRepayment": true,
"outstandingDebt": "50412.881",
"borrowAssetSymbol": "USDC",
"borrowAssetAddress": "0x98ad0ca091552e23c564b41c74282e5343d03e8a",
"signer": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"nonce": "0",
"transactionFee": {
"kind": "REPAY",
"fee": "2.0",
"feeRaw": "2000000",
"message": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan."
},
"warning": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan.",
"description": "Sign this EIP-2612 permit from the borrower wallet 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 over 50515.706763 USDC (the outstanding debt plus a 20 bps accrual buffer and the repayment fee), then send the repayment with all:true and the permit; the market pulls exactly the true debt and the fee and the second step clears the leftover allowance"
}
}
{
"success": false,
"error": {
"code": "PERMIT_UNSUPPORTED",
"message": "The settlement asset USDT does not implement EIP-2612, so single-signature repayment is not available on this market. Use the approve-then-repay steps instead."
}
}
{
"success": false,
"error": {
"code": "PERMIT_DOMAIN_UNRESOLVED",
"message": "The EIP-712 domain of USDC could not be rebuilt from its name() and version(): no candidate reproduces the token's DOMAIN_SEPARATOR, so a permit quoted here could not be verified. Sign the permit with tooling that knows the token's domain, or use the approve-then-repay steps."
}
}
Error Codes
| Code | HTTP | Cause |
|---|---|---|
NO_MARKET_ADDRESS | 400 | The market has no on-chain address recorded |
MISSING_MARKET_ID | 400 | marketId is longer than 100 characters |
VALIDATION_ERROR | 400 | loanId is not a positive integer, neither amount nor all: true was supplied, amount is malformed or carries more decimal places than the borrow asset supports, or deadlineSeconds is not an integer from 60 to 86400 |
LOAN_NOT_ACTIVE | 400 | The loan is closed and can no longer be repaid |
NO_OUTSTANDING_DEBT | 400 | all was set on a loan that owes nothing |
PERMIT_QUOTE_FAILED | 400 | The quote could not be prepared and no more specific code applied |
LOAN_NOT_FOUND | 404 | No such loan on this market |
MARKET_NOT_FOUND | 404 | No market with this ID on your instance |
PERMIT_UNSUPPORTED | 409 | The settlement asset does not implement EIP-2612, or its support could not be verified. Use the approve-then-repay steps |
PERMIT_DOMAIN_UNRESOLVED | 409 | No domain rebuilt from the token’s name() and version() reproduces its DOMAIN_SEPARATOR |
LOAN_STATE_UNAVAILABLE | 503 | The loan could not be read from the network right now. Retry shortly |
MARKET_STATE_UNAVAILABLE | 503 | The market’s transaction fees could not be read. Retry shortly |
PERMIT_UNVERIFIABLE | 503 | The token’s permit domain or the borrower’s nonce could not be read or decoded. Retry, or use the approve-then-repay steps |
⌘I
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/positions/{marketId}/permit-quote" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"loanId": 5, "all": true}'
import { ethers } from 'ethers';
const base = `https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}`;
const headers = {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
};
const quoteRes = await fetch(`${base}/permit-quote`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, all: true })
});
const { data: quote } = await quoteRes.json();
const { domain, types, message } = quote.typedData;
const signature = await borrowerWallet._signTypedData(domain, types, message);
const { v, r, s } = ethers.utils.splitSignature(signature);
const repayRes = await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({
...quote.repay,
permit: { value: quote.permit.value, deadline: quote.permit.deadline, v, r, s }
})
});
const { data: repayment } = await repayRes.json();
const hashes: string[] = [];
for (const step of repayment.steps) {
const tx = await borrowerWallet.sendTransaction(step.transaction);
await tx.wait();
hashes.push(tx.hash);
}
await fetch(`${base}/repay`, {
method: 'POST',
headers,
body: JSON.stringify({ loanId: 5, txHash: hashes[repayment.confirmStepIndex] })
});
{
"success": true,
"data": {
"success": true,
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "USD Coin",
"version": "2",
"chainId": 11155111,
"verifyingContract": "0x98ad0ca091552e23c564b41c74282e5343d03e8a"
},
"types": {
"Permit": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" },
{ "name": "nonce", "type": "uint256" },
{ "name": "deadline", "type": "uint256" }
]
},
"primaryType": "Permit",
"message": {
"owner": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"spender": "0x70a0e25c7b768b87e658348b3b577678a173e038",
"value": "50515706763",
"nonce": "0",
"deadline": 1790418600
}
},
"permit": {
"value": "50515.706763",
"deadline": 1790418600
},
"repay": {
"loanId": "5",
"all": true
},
"fullRepayment": true,
"outstandingDebt": "50412.881",
"borrowAssetSymbol": "USDC",
"borrowAssetAddress": "0x98ad0ca091552e23c564b41c74282e5343d03e8a",
"signer": "0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3",
"nonce": "0",
"transactionFee": {
"kind": "REPAY",
"fee": "2.0",
"feeRaw": "2000000",
"message": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan."
},
"warning": "This market charges a repayment fee of about 2.0 USDC on top of the debt, taken from the payment before it reduces the loan. The amount approved and offered covers the debt, the accrual buffer and the fee, and the market pulls only what closes the loan.",
"description": "Sign this EIP-2612 permit from the borrower wallet 0xaBc7f1093D5E26B804a1C3f78dE025916B47C0d3 over 50515.706763 USDC (the outstanding debt plus a 20 bps accrual buffer and the repayment fee), then send the repayment with all:true and the permit; the market pulls exactly the true debt and the fee and the second step clears the leftover allowance"
}
}
{
"success": false,
"error": {
"code": "PERMIT_UNSUPPORTED",
"message": "The settlement asset USDT does not implement EIP-2612, so single-signature repayment is not available on this market. Use the approve-then-repay steps instead."
}
}
{
"success": false,
"error": {
"code": "PERMIT_DOMAIN_UNRESOLVED",
"message": "The EIP-712 domain of USDC could not be rebuilt from its name() and version(): no candidate reproduces the token's DOMAIN_SEPARATOR, so a permit quoted here could not be verified. Sign the permit with tooling that knows the token's domain, or use the approve-then-repay steps."
}
}
