Skip to main content

Developers

Developers define a Script that is part of an Acurast Job. This Script defines in code e.g., what API is being called for the price of an asset, if certificate pinning is being done, calculations relevant to the use case. The Acurast Job and its associated Script is being run by Data Transmitter in the Acurast Trusted Execution Environment which yields not only the output of the Script but also makes it verifiable.

It's completely up to you as the developer to specify the needs for your specific use case.

Read more on the steps on How To Get Started.

You can use the Playground to test your Script and simmulate the outputs.

Examples

We have gathered some examples that show some of the use cases.

HTTPS GET Request

Request from an API with the result of an asset price.

httpGET(
"https://api.binance.com/api/v3/ticker/price?symbol=BNBBTC",
{},
(response, certificate) => {
console.log("response", response);
const price = JSON.parse(response)["price"] * 10 ** 6;
console.log("price", price);
const payload = { price: price, timestamp: Date.now() / 1000 };
const packedPayload = pack(payload);
const signature = sign(payload);
httpPOST(
"https://oracle.free.beeceptor.com",
JSON.stringify({
signature: signature,
certificate: certificate,
payload: packedPayload,
}),
{},
(response, certificate) => {},
(errorMessage) => {}
);
},
(errorMessage) => {}
);
https://example.com

Verifiable Randomness

Getting a randomized output from the Trusted Execution Environment.

return fulfill(generateSecureRandomHex());
https://example.com

Complete Example

This is a complete example that includes multiple price points and calculations.

  • Fetches prices for multiple assets from multiple endpoints
  • Does certificate pinning to ensure that the data is coming from the instructed sources
  • Calculations for an index with multiple assets
  • Calculations for the median
  • Defines a minimal amount of sources
const INTERVAL = 900 * 1000;

const MINIMUM_SOURCES_PER_PRICE = 2;

const PREVIOUS_EPOCH_START_MILLIS =
(Math.floor(Date.now() / INTERVAL) - 1) * INTERVAL;
const PREVIOUS_EPOCH_END_MILLIS =
Math.floor(Date.now() / INTERVAL) * INTERVAL - 1;
const PREVIOUS_EPOCH_START_SECONDS = Math.floor(
PREVIOUS_EPOCH_START_MILLIS / 1000
);
const PREVIOUS_EPOCH_END_SECONDS = Math.floor(PREVIOUS_EPOCH_END_MILLIS / 1000);

const PREVIOUS_EPOCH_START_ISO = new Date(
PREVIOUS_EPOCH_START_MILLIS
).toISOString();
const PREVIOUS_EPOCH_END_ISO = new Date(
PREVIOUS_EPOCH_END_MILLIS
).toISOString();

// for index to USDT
const BINANCE_SYMBOLS = [
"UNIUSDT",
"CAKEUSDT",
"LUNAUSDT",
"LINKUSDT",
"AAVEUSDT",
];
const KUCOIN_SYMBOLS = [
"UNI-USDT",
"CAKE-USDT",
"LUNA-USDT",
"LINK-USDT",
"AAVE-USDT",
];
const GATEIO_SYMBOLS = [
"UNI_USDT",
"CAKE_USDT",
"LUNA_USDT",
"LINK_USDT",
"AAVE_USDT",
];

// for USDT<>USD
const COINBASE_SYMBOLS = ["BTC-USD", "XTZ-USD", "USDT-USD"];
const BITFINEX_SYMBOLS = ["BTCUSD", "XTZUSD", "USTUSD"];
const BINANCE_US_SYMBOLS = ["BTCUSD", "XTZUSD", "USDTUSD"];

const PRICE_PRECISION = 10 ** 6;

// for index to USDT
const BINANCE_TEMPLATE = `https://api.binance.com/api/v3/klines?symbol=<<SYMBOL>>&interval=15m&startTime=${PREVIOUS_EPOCH_START_MILLIS}&endTime=${PREVIOUS_EPOCH_END_MILLIS}`;
const KUCOIN_TEMPLATE = `https://api.kucoin.com/api/v1/market/candles?symbol=<<SYMBOL>>&type=15min&startAt=${PREVIOUS_EPOCH_START_SECONDS}&endAt=${PREVIOUS_EPOCH_END_SECONDS}`;
const GATE_IO_TEMPLATE = `https://api.gateio.ws/api/v4/spot/candlesticks/?currency_pair=<<SYMBOL>>&interval=15m&from=${PREVIOUS_EPOCH_START_SECONDS}&to=${PREVIOUS_EPOCH_END_SECONDS}`;

// for USDT<>USD
const BINANCE_US_TEMPLATE = `https://api.binance.us/api/v3/klines?symbol=<<SYMBOL>>&interval=15m&startTime=${PREVIOUS_EPOCH_START_MILLIS}&endTime=${PREVIOUS_EPOCH_END_MILLIS}`;
const COINBASE_TEMPLATE = `https://api.pro.coinbase.com/products/<<SYMBOL>>/candles?granularity=900&start=${PREVIOUS_EPOCH_START_ISO}&end=${PREVIOUS_EPOCH_END_ISO}`;
const BITFINEX_TEMPLATE = `https://api-pub.bitfinex.com/v2/candles/trade:15m:t<<SYMBOL>>/hist?start=${PREVIOUS_EPOCH_START_MILLIS}&end=${PREVIOUS_EPOCH_END_MILLIS}`;

const BINANCE_CONFIG = {
url: BINANCE_TEMPLATE,
exchange_id: "BNN",
timestamp_factor: 1,
timestamp_index: 0,
close_index: 4,
certificate:
"0D:ED:57:7F:B9:1B:4C:8E:B2:36:50:10:4A:BA:08:FF:6C:86:77:75:4E:32:8D:02:09:E1:BE:F3:42:7B:BD:D4",
};
const KUCOIN_CONFIG = {
url: KUCOIN_TEMPLATE,
exchange_id: "KUC",
timestamp_factor: 1000,
timestamp_index: 0,
close_index: 2,
certificate:
"A7:3F:12:F8:0A:A1:87:C0:97:86:B1:E1:0E:03:73:9C:3C:71:73:44:DA:32:C7:77:21:57:0F:48:5C:FC:18:6F",
};
const GATE_IO_CONFIG = {
url: GATE_IO_TEMPLATE,
exchange_id: "GAT",
timestamp_factor: 1000,
timestamp_index: 0,
close_index: 2,
certificate:
"45:8B:33:B8:2F:54:69:32:7B:FB:0B:02:51:29:08:E6:6F:62:BA:2E:93:0A:F7:9E:CB:03:07:C2:E8:E9:DC:74",
};

const BINANCE_US_CONFIG = {
url: BINANCE_US_TEMPLATE,
exchange_id: "BNU",
timestamp_factor: 1,
timestamp_index: 0,
close_index: 4,
certificate:
"9E:94:90:DB:57:6D:B8:17:A4:68:C4:30:9D:83:76:C1:49:04:26:C1:2E:55:93:9D:E0:92:A6:13:16:14:E0:FA",
};
const COINBASE_CONFIG = {
url: COINBASE_TEMPLATE,
exchange_id: "CBP",
timestamp_factor: 1000,
timestamp_index: 0,
close_index: 4,
certificate:
"8B:70:72:2E:AE:DC:63:68:D9:EA:59:6F:B3:30:C1:E7:F8:5F:30:81:8D:7C:90:05:73:BC:64:04:61:D1:03:03",
};
const BITFINEX_CONFIG = {
url: BITFINEX_TEMPLATE,
exchange_id: "BFX",
timestamp_factor: 1,
timestamp_index: 0,
close_index: 2,
certificate:
"57:E3:2B:C3:C5:AC:D8:86:E4:E4:25:9C:CB:A2:40:29:1F:07:7D:E1:61:21:2B:0F:B0:EC:8E:71:4A:82:C8:D1",
};

const INITIAL_PRICES = {
AAVEUSD: 150.8,
CAKEUSD: 6.52,
LINKUSD: 14.76,
LUNAUSD: 94.46,
UNIUSD: 9.36,
};

const WEIGHTS = {
AAVE: 0.0406,
CAKE: 0.0368,
LINK: 0.1335,
LUNA: 0.6654,
UNI: 0.1238,
};

const LAST_REBALANCING_PRICE = 1.21617108;

const internalFetch = (config, symbols) => {
return symbols.map((symbol) => {
return new Promise((resolve, reject) => {
httpGET(
config.url.replace("<<SYMBOL>>", symbol),
{},
(rawResponse, certificate) => {
let response = JSON.parse(rawResponse);
if (config.exchange_id === "KUC") {
response = response["data"];
}

if (certificate === config.certificate) {
const payload = {
symbol: symbol
.replace("-", "")
.replace("_", "")
.replace("UST", "USDT"),
exchange_id: config.exchange_id,
timestamp:
response[0][config.timestamp_index] * config.timestamp_factor,
close: parseFloat(response[0][config.close_index]),
certificate: certificate,
};
resolve(payload);
} else {
reject("certificate does not match");
}
},
(errorMessage) => {
reject(errorMessage);
}
);
});
});
};

const median = (values) => {
values.sort((a, b) => a - b);

if (values.length % 2 == 0) {
return (
(values[Math.floor(values.length / 2)] +
values[Math.floor(values.length / 2) - 1]) /
2.0
);
} else {
return values[Math.floor(values.length / 2)];
}
};

const promises = [
...internalFetch(BINANCE_CONFIG, BINANCE_SYMBOLS),
...internalFetch(KUCOIN_CONFIG, KUCOIN_SYMBOLS),
...internalFetch(GATE_IO_CONFIG, GATEIO_SYMBOLS),

...internalFetch(BINANCE_US_CONFIG, BINANCE_US_SYMBOLS),
...internalFetch(COINBASE_CONFIG, COINBASE_SYMBOLS),
...internalFetch(BITFINEX_CONFIG, BITFINEX_SYMBOLS),
];

Promise.allSettled(promises).then((results) => {
const fulfilledPayloads = results
.filter((result) => result.status === "fulfilled")
.map((result) => result.value)
.filter((item) => item.timestamp === PREVIOUS_EPOCH_START_MILLIS);

const prices = fulfilledPayloads.reduce((previousValue, currentValue) => {
if (currentValue.symbol in previousValue) {
previousValue[currentValue.symbol].push(currentValue.close);
} else {
previousValue[currentValue.symbol] = [currentValue.close];
}
return previousValue;
}, {});

const indexPrice =
(LAST_REBALANCING_PRICE *
PRICE_PRECISION *
(WEIGHTS["UNI"] * (median(prices["UNIUSDT"]) / INITIAL_PRICES["UNIUSD"]) +
WEIGHTS["LINK"] *
(median(prices["LINKUSDT"]) / INITIAL_PRICES["LINKUSD"]) +
WEIGHTS["LUNA"] *
(median(prices["LUNAUSDT"]) / INITIAL_PRICES["LUNAUSD"]) +
WEIGHTS["CAKE"] *
(median(prices["CAKEUSDT"]) / INITIAL_PRICES["CAKEUSD"]) +
WEIGHTS["AAVE"] *
(median(prices["AAVEUSDT"]) / INITIAL_PRICES["AAVEUSD"]))) /
median(prices["USDTUSD"]);

const sources_count = Math.min(
...Object.values(prices).map((values) => values.length)
);

if (sources_count >= MINIMUM_SOURCES_PER_PRICE) {
const oraclePayload = {
timestamp: PREVIOUS_EPOCH_START_SECONDS,
defi_price: Math.round(indexPrice),
xtz_price: Math.round(median(prices["XTZUSD"]) * PRICE_PRECISION),
btc_price: Math.round(median(prices["BTCUSD"]) * PRICE_PRECISION),
};
console.log(fulfill(oraclePayload));
}
});
https://example.com