Oracle
Sign Price
Produce an EIP-712 signed price for atomic use in a transaction
POST
/
lending-external-securities-v2
/
api
/
markets
/
{marketId}
/
sign-price
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/sign-price" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"price": 105.40, "validitySeconds": 300}'
const signRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/sign-price`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({ price: 105.40, validitySeconds: 300 })
}
);
const { data: signed } = await signRes.json();
const { domain, types, message } = signed.typedData;
const signature = await priceSignerWallet._signTypedData(domain, types, message);
const loanRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: '0xabc7f1093d5e26b804a1c3f78de025916b47c0d3',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature
}
})
}
);
{
"success": true,
"data": {
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "SecurityPriceOracle",
"version": "1",
"chainId": 11155111,
"verifyingContract": "0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423"
},
"types": {
"PriceUpdate": [
{ "name": "price", "type": "uint256" },
{ "name": "timestamp", "type": "uint256" },
{ "name": "validUntil", "type": "uint256" }
]
},
"primaryType": "PriceUpdate",
"message": {
"price": "105400000",
"timestamp": 1718445600,
"validUntil": 1718445900
}
},
"display": {
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
},
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
}
}
{
"success": false,
"error": {
"code": "SIGNER_NOT_AUTHORIZED",
"message": "Wallet 0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52 is not authorized on oracle 0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423, and this market declared no price signer to attest for it. The oracle owner must call setAuthorizedSigner, or pass signerAddress for the key that holds price authority here.",
"signerAddress": "0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52"
}
}
{
"success": false,
"error": {
"code": "MISSING_PRICE",
"message": "A positive NAV price is required"
}
}
Returns an EIP-712 payload for a NAV, ready to sign. Sign it with a key authorized on the market’s oracle. Then pass the price and the signature as
This is the alternative to Sync Oracle Price. Signing costs no gas and writes nothing on-chain; the price lands only with the transaction that carries it. That suits a borrower who needs a fresh valuation without waiting for a scheduled push. A key that signs outside this instance can also post its signature to Record Price Attestation, which lets a later push submit it.
An
The signature is bound to one oracle on one chain through
signedPrice on Open Loan, Borrow More, Withdraw Collateral, Repay or Liquidate Loan. The contract applies the price and the action in one transaction.
A signed price does not stay private to the transaction that carries it. When it lands, the oracle stores it, and every loan in the market is valued at it from then on. Sign only a price the market’s price authority stands behind.
ORACLE-priced market reads an external feed and refuses every attested price. This endpoint refuses it with EXTERNAL_FEED_ACTIVE.
A FIXED market’s oracle refuses every signed price too, so there is nothing to sign. This endpoint answers 409 FIXED_PRICE_ACTIVE with details.markDownOnly: true. The price authority can only mark a fixed price down, through Sync Oracle Price.
Path Parameters
string
required
Body Parameters
number
required
Net asset value per collateral token, denominated in the market’s borrow asset. Must be greater than zero, and a numeric string is accepted too. More decimal places than the borrow asset carries fail with
SIGNING_FAILED.integer
default:"300"
Signature lifetime in seconds. Values below 60 are raised to 60 and values above 3600 are lowered to 3600. Zero or a non-numeric value falls back to 300.
string
The wallet that will sign, when price authority sits with a key this instance does not hold. Supply it and the payload is built for that address and its authorization is checked against the oracle. Omit it and the signer is resolved in order: the price signer the market’s lender of record declared, then your instance’s first verified wallet. A value that is not a 0x-prefixed 40-character hex address is ignored.
Response Fields
object
Show child attributes
Show child attributes
string
Always
SIGN_TYPED_DATA.object
The EIP-712 payload to sign, carrying
domain, types, primaryType, and message. Pass it straight to signTypedData.object
The same values in readable form, also flattened onto
data itself.string
Price in base units at the borrow asset’s decimals. Pass this value, not
priceUsd, in the signedPrice object.string
The same price as a human-readable decimal string. Informational.
integer
Unix seconds the price is stamped at. Never ahead of chain time.
integer
Unix seconds after which the signature is rejected.
string
The wallet expected to sign. Signing with any other wallet produces a signature the oracle rejects.
No signature is returned. You produce it by signing
typedData. The contract then reads price, timestamp, validUntil, and your signature. Pass the first three through unchanged.Signature Structure
domain = {
name: "SecurityPriceOracle",
version: "1",
chainId: <chain the market is deployed on>,
verifyingContract: <market oracle address>
}
PriceUpdate = {
price: uint256
timestamp: uint256
validUntil: uint256
}
verifyingContract and chainId, and expires at validUntil. It carries no nonce and names no submitter, so anyone who holds it can submit it until it expires. It lands at most once, because the oracle only accepts a timestamp newer than the price it holds. Keep validitySeconds tight, and treat a signed price as a bearer credential over its lifetime.
What the Oracle Checks When the Price Lands
The oracle judges the signature again inside the transaction that carries it, and a refusal reverts that whole transaction. It refuses a signature pastvalidUntil, one dated in the future or older than maxPriceAge, and one whose signer is neither an authorized signer nor the owner. It also refuses a price outside the deviation limit, both against the current price and against the price that opened the current one-hour window.
The API runs both deviation checks before it builds the payload, whenever the oracle’s price can be read. A price outside them is refused with PRICE_DEVIATION_TOO_LARGE or PRICE_WINDOW_DEVIATION_TOO_LARGE, carrying the same fields as on Sync Oracle Price.
A signed price dated no later than the price the oracle already holds is skipped rather than refused. The transaction then runs against the stored price, which must not be stale.
Check a signature without spending gas using Verify Signed Price.
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/sign-price" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"price": 105.40, "validitySeconds": 300}'
const signRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/sign-price`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({ price: 105.40, validitySeconds: 300 })
}
);
const { data: signed } = await signRes.json();
const { domain, types, message } = signed.typedData;
const signature = await priceSignerWallet._signTypedData(domain, types, message);
const loanRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: '0xabc7f1093d5e26b804a1c3f78de025916b47c0d3',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature
}
})
}
);
{
"success": true,
"data": {
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "SecurityPriceOracle",
"version": "1",
"chainId": 11155111,
"verifyingContract": "0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423"
},
"types": {
"PriceUpdate": [
{ "name": "price", "type": "uint256" },
{ "name": "timestamp", "type": "uint256" },
{ "name": "validUntil", "type": "uint256" }
]
},
"primaryType": "PriceUpdate",
"message": {
"price": "105400000",
"timestamp": 1718445600,
"validUntil": 1718445900
}
},
"display": {
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
},
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
}
}
{
"success": false,
"error": {
"code": "SIGNER_NOT_AUTHORIZED",
"message": "Wallet 0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52 is not authorized on oracle 0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423, and this market declared no price signer to attest for it. The oracle owner must call setAuthorizedSigner, or pass signerAddress for the key that holds price authority here.",
"signerAddress": "0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52"
}
}
{
"success": false,
"error": {
"code": "MISSING_PRICE",
"message": "A positive NAV price is required"
}
}
Error Codes
Every refusal from the signing step carriessignerAddress in the error object. It names the rejected wallet on SIGNER_NOT_AUTHORIZED and is null otherwise.
| Code | HTTP | Cause |
|---|---|---|
NO_ORACLE | 400 | The market records no oracle address |
MISSING_PRICE | 400 | price is missing, unparseable, or not greater than zero |
EXTERNAL_FEED_ACTIVE | 400 | The market’s oracle reads an external feed and refuses signed prices |
SIGNER_NOT_AUTHORIZED | 400 | The resolved signer is neither an authorized signer nor the owner of the market’s oracle |
WALLET_NOT_CONFIGURED | 400 | No signerAddress was given, the market declared no price signer, and this instance has no verified wallet |
PRICE_DEVIATION_TOO_LARGE | 400 | The step from the on-chain price exceeds maxDeviationBps. The error carries currentPrice and maxDeviationPercent |
PRICE_WINDOW_DEVIATION_TOO_LARGE | 400 | The movement within the current one-hour window would exceed maxDeviationBps. The error adds windowAnchorPrice, windowStartedAt, windowResetsAt, acceptedLow and acceptedHigh |
SIGNING_FAILED | 400 | The payload could not be built and no more specific code applied |
MISSING_MARKET_ID | 400 | marketId is longer than 100 characters |
MARKET_NOT_FOUND | 404 | No market with this ID on your instance |
FIXED_PRICE_ACTIVE | 409 | The market has a fixed price, and its oracle refuses every signed price. details.markDownOnly is true. Mark the price down through Sync Oracle Price instead |
PRICE_STATE_UNAVAILABLE | 503 | Whether the market’s oracle holds a fixed price could not be read. Retry shortly |
⌘I
curl -X POST "https://api.trusset.org/lending-external-securities-v2/api/markets/{marketId}/sign-price" \
-H "X-API-Key: trusset_your_key_here" \
-H "Content-Type: application/json" \
-d '{"price": 105.40, "validitySeconds": 300}'
const signRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/markets/${marketId}/sign-price`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({ price: 105.40, validitySeconds: 300 })
}
);
const { data: signed } = await signRes.json();
const { domain, types, message } = signed.typedData;
const signature = await priceSignerWallet._signTypedData(domain, types, message);
const loanRes = await fetch(
`https://api.trusset.org/lending-external-securities-v2/api/positions/${marketId}/open-loan`,
{
method: 'POST',
headers: {
'X-API-Key': 'trusset_your_key_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({
collateralAmount: '1000',
borrowAmount: '50000',
borrowerAddress: '0xabc7f1093d5e26b804a1c3f78de025916b47c0d3',
signedPrice: {
price: signed.price,
timestamp: signed.timestamp,
validUntil: signed.validUntil,
signature
}
})
}
);
{
"success": true,
"data": {
"action": "SIGN_TYPED_DATA",
"typedData": {
"domain": {
"name": "SecurityPriceOracle",
"version": "1",
"chainId": 11155111,
"verifyingContract": "0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423"
},
"types": {
"PriceUpdate": [
{ "name": "price", "type": "uint256" },
{ "name": "timestamp", "type": "uint256" },
{ "name": "validUntil", "type": "uint256" }
]
},
"primaryType": "PriceUpdate",
"message": {
"price": "105400000",
"timestamp": 1718445600,
"validUntil": 1718445900
}
},
"display": {
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
},
"price": "105400000",
"priceUsd": "105.4",
"timestamp": 1718445600,
"validUntil": 1718445900,
"signer": "0x9c1e57a2d0b34f86e7a15c9d2b4f60e38a7d4b70"
}
}
{
"success": false,
"error": {
"code": "SIGNER_NOT_AUTHORIZED",
"message": "Wallet 0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52 is not authorized on oracle 0x3b25752c1459c5cf1b0bfcfdf0d56883c8047423, and this market declared no price signer to attest for it. The oracle owner must call setAuthorizedSigner, or pass signerAddress for the key that holds price authority here.",
"signerAddress": "0x1234f9a07c6b53d81e2a4f70c9b385d6014a7e52"
}
}
{
"success": false,
"error": {
"code": "MISSING_PRICE",
"message": "A positive NAV price is required"
}
}
