blueprints.js

blueprints.js is a free and open-source TypeScript library for working with Blueprints. It can be installed from npm.

npm i @blueprintsfi/blueprints.js

Note that blueprints.js does not provide functionality for interacting with the smart contracts directly.

Defining tokens

import { Basket, Blueprint, Fraction, MapBytes, NativeToken, WithMetadata } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

// Represents the chain's native token (e.g. ETH).
const eth = new NativeToken(9n, {
    decimals: 18n,
});
assert.strictEqual(eth.toString(), "9 NativeToken<18>");

// Tokens can be also parsed from their string representations. This
// position represents an ERC20 token with address
// `0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2` that has 18 decimals.
const erc20 = Blueprint.fromString(
    "4.5 ERC20Wrapper<0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 18>",
);
assert.strictEqual(erc20.params.address, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
assert.strictEqual(erc20.params.decimals, 18n);

// Represents a basket – a single token that wraps two positions inside.
const basket = new Basket(new Fraction(2n, 3n), {
    tokens: [eth, erc20],
});

assert.strictEqual(basket.toString(), "~0.666666666666666666667 Basket<[9 NativeToken<18>, 4.5 ERC20Wrapper<0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 18>]>");

// Attaches application-specific bytes to a token without changing its token id.
const annotated = new WithMetadata(2n, {
    token: new NativeToken(1n, { decimals: 18n }),
    metadata: new MapBytes({
        user_comment: "I know I'm overexposed but the price is down right now",
    }),
});
assert.strictEqual(
    annotated.toString(),
    `2 WithMetadata<1 NativeToken<18>, {"user_comment":"I know I'm overexposed but the price is down right now"}>`,
);
assert.strictEqual(annotated.externalTokenId().toHex(), annotated.params.token.externalTokenId().toHex());

Each Blueprint has its own docs page, where you can learn about its parameters. The token parameters are immutable, unlike its amount.

yesToken, noToken

yesToken and noToken are helper functions for creating ComposablePrediction tokens with a single oracle, whose response is binary.

import {
    yesToken,
    noToken,
    ERC20Wrapper,
    ComposablePrediction,
    MapBytes,
    MultisigOracle,
    HashedBytes,
} from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const oracle = new MultisigOracle({
    addresses: ["0xCA2421bE4AA793c9F95faef57811F4eD360f54e1"],
    threshold: 1n,
    feedId: new HashedBytes(new MapBytes({
        market: "BTC",
        condition: "above 100000",
        time: "2027-01-01 00:00:00 UTC",
    })),
});
const collateral = new ERC20Wrapper(20n, {
    decimals: 18n,
    address: "0x6fc2e9b25fa19eeC97a05A18aF69332477BA3728",
});

const yes = yesToken(collateral, oracle);
assert(yes instanceof ComposablePrediction);

// This is equivalent to:
const yes2 = new ComposablePrediction(1n, {
    collateral,
    constraints: [
        { oracle, startRange: 0n, endRange: 1n },
    ],
});
assert.strictEqual(yes.externalTokenId().toHex(), yes2.externalTokenId().toHex());

const no = noToken(collateral, oracle);
assert(no instanceof ComposablePrediction);

// This is equivalent to:
const no2 = new ComposablePrediction(1n, {
    collateral,
    constraints: [
        { oracle, startRange: 1n, endRange: 0n },
    ],
});
assert.strictEqual(no.externalTokenId().toHex(), no2.externalTokenId().toHex());

String representation

Each token has exactly one string representation. It can be obtained by calling the toString method. A token can be parsed from its string representation using the Blueprint.fromString function.

Tokens' amounts in their string representations might be approximated, as seen in the basket defined in the defining tokens section. However, if that is the case, enough decimals will be included so that the exact amount can be recovered unambiguously.

toString()

Returns the token's unique string representation.

It optionally takes a single format parameter which can be raw or html. If it is omitted or set to raw, the raw string representation is returned. If set to html, the string representation is formatted as HTML, which means parts of it are wrapped in <span> tags with different classes:

import { Basket, ERC20Wrapper, Fraction, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = new NativeToken(9n, {
    decimals: 18n,
});

assert.strictEqual(eth.toString(), "9 NativeToken<18>");
assert.strictEqual(
    eth.toString("html"),
    `<span class="bp-amount">9</span> <span class="bp-blueprint">NativeToken</span>&lt;<span class="bp-int">18</span>&gt;`,
);

Blueprint.fromString()

Parses a token from its unique string representation. Throws a SyntaxError if an invalid representation is passed.

import { Blueprint, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = Blueprint.fromString("9 NativeToken<18>");
assert(eth instanceof NativeToken);
assert.strictEqual(eth.params.decimals, 18n);

params

Token's params. It has the same type as the second argument of the Blueprint's constructor.

import { Blueprint } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const erc20 = Blueprint.fromString("10 ERC20Wrapper<0x0ABb8fFa90597b00a3E1927d78dbaBe23573ED12, 18>");
assert.strictEqual(erc20.params.address, "0x0ABb8fFa90597b00a3E1927d78dbaBe23573ED12");
assert.strictEqual(erc20.params.decimals, 18n);

Fraction

Fraction is a helper class which has two read-only attributes – numerator and denominator. Both of them are bigints. The numerator has to be non-negative and the denominator has to be positive. Each fraction has a unique strict representation among fractions with the same denominators that can be obtained using the toString method. Fraction can be parsed from its string representation using the Fraction.fromString function which takes the desired fraction denominator.

import { Fraction } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const half = new Fraction(2n, 4n);
assert.strictEqual(half.numerator, 2n);
assert.strictEqual(half.denominator, 4n);
assert.strictEqual(half.toString(), "0.5");

const third = new Fraction(1n, 3n);
assert.strictEqual(third.toString(), "~0.333");

const zero = new Fraction(0n, 1n);
assert.strictEqual(zero.toString(), "0");

const twoThirds = Fraction.fromString("~0.667", 3n);
assert.strictEqual(twoThirds.numerator, 2n);
assert.strictEqual(twoThirds.denominator, 3n);

Multiplier

Each token has a multiplier which depends on its parameters (but not the amount). It is the number of wei tokens a single token can be split into. For example, the only parameter of NativeToken<18> indicates that it has 18 decimals and a multiplier of 101810^{18}.

A multiplier doesn't have to be a power of 10. For an example, take a look at the basket token defined in the defining tokens section, which has a multiplier of 4.510184.5 \cdot 10^{18}.

amount

A Fraction. The numerator is equal to the amount in wei and the denominator is equal to the token multiplier. Note that this implies that all tokens created by a single Blueprint with the same parameters have the same amount.denominator.

Note that if the denominator of the Fraction passed to the Blueprint's constructor or to the setAmount method isn't equal to the multiplier, the fraction will be automatically multiplied. However, an error will be thrown if the exact desired amount cannot be expressed.

import { Fraction, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = new NativeToken(new Fraction(1n, 2n), {
    decimals: 18n,
});

assert.strictEqual(eth.amount.numerator, 5n * 10n ** 17n);
assert.strictEqual(eth.amount.denominator, 10n ** 18n);
assert.strictEqual(eth.amount.toString(), "0.5");

eth.setWeiAmount(2n * 10n ** 18n);
assert.strictEqual(eth.amount.numerator, 2n * 10n ** 18n);
assert.strictEqual(eth.amount.denominator, 10n ** 18n);
assert.strictEqual(eth.amount.toString(), "2");

weiAmount

A shortcut for amount.numerator.

multiplier

A shortcut for amount.denominator.

setAmount()

Sets token's amount. Takes a Fraction or a bigint.

import { Fraction, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = new NativeToken(1n, {
    decimals: 18n,
});

eth.setAmount(new Fraction(1n, 2n));
assert.strictEqual(eth.amount.numerator, 5n * 10n ** 17n);
assert.strictEqual(eth.amount.denominator, 10n ** 18n);

eth.setAmount(0n);
assert.strictEqual(eth.amount.numerator, 0n);
assert.strictEqual(eth.amount.denominator, 10n ** 18n);

setWeiAmount()

Takes a single bigint – the new token amount in wei.

import { Fraction, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = new NativeToken(1n, {
    decimals: 18n,
});

eth.setWeiAmount(2n * 10n ** 18n);
assert.strictEqual(eth.amount.numerator, 2n * 10n ** 18n);
assert.strictEqual(eth.amount.denominator, 10n ** 18n);
assert.strictEqual(eth.amount.toString(), "2");

internalTokenId()

Returns the token's internal id as an Uint8Array of length 32.

externalTokenId()

Returns the token's external id as an Uint8Array of length 32.

blueprintAddress()

Returns the Blueprint's smart contract address.

copy()

Copies the token.

import { ERC20Wrapper } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const eth = new ERC20Wrapper(20n, {
    address: "0xF85C1127D6157993d492EBEcE36a3b820DAF8fF7",
    decimals: 18n,
});

const eth2 = eth.copy();
assert(eth2 instanceof ERC20Wrapper);
// Since token's params are immutable, the `params` object is the same (a copy
// is not made).
assert(eth.params === eth2.params);

eth2.setAmount(40n);
assert.strictEqual(eth2.amount.toString(), "40");
// Original token stays untouched.
assert.strictEqual(eth.amount.toString(), "20");

Bytes

Some blueprints and oracles take bytes as one of their parameters. Often the bytes have some special interpretation. For example, a feed id of a MultisigOracle might be a hash of a string. This should be expressed in the oracle's string representation. To achieve this, those blueprints and oracles take a Bytes object. It is an abstract class that has several subclasses, each of them corresponding to a different interpretation.

RawBytes

Represents bytes that have no special meaning. The constructor takes either a hex-encoded buffer as string (without 0x prefix) or Uint8Array.

import { MultisigOracle, RawBytes } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const feedId = "e53dcc3bd5f13fc11bfa1907103c388b67d7b6018e4c301aa61b3cdafb1452af";

const oracle = new MultisigOracle({
    addresses: ["0xCA2421bE4AA793c9F95faef57811F4eD360f54e1"],
    threshold: 1n,
    feedId: new RawBytes(feedId), // `Uint8Array.fromHex(feedId)` also works
});
assert.strictEqual(oracle.toString(), "MultisigOracle<[0xCA2421bE4AA793c9F95faef57811F4eD360f54e1], 1, 0xe53dcc3bd5f13fc11bfa1907103c388b67d7b6018e4c301aa61b3cdafb1452af>");

StringBytes

Represents bytes that should be interpreted as a string. The string can contain only printable ASCII characters (including space) other than " and \.

import { HashedBytes, MultisigOracle, StringBytes } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const feedId = "e53dcc3bd5f13fc11bfa1907103c388b67d7b6018e4c301aa61b3cdafb1452af";

const oracle = new MultisigOracle({
    addresses: ["0xCA2421bE4AA793c9F95faef57811F4eD360f54e1"],
    threshold: 1n,
    feedId: new HashedBytes(new StringBytes("bitcoin above 100000 on 2027-01-01 00:00:00 UTC")),
});
assert.strictEqual(
    oracle.toString(),
    `MultisigOracle<[0xCA2421bE4AA793c9F95faef57811F4eD360f54e1], 1, keccak256("bitcoin above 100000 on 2027-01-01 00:00:00 UTC")>`,
);

MapBytes

Represents bytes that should be interpreted as a JSON-encoded string-to-string map. Keys and values can contain only printable ASCII characters (including space) other than " and \.

import { MapBytes, WithMetadata, NativeToken } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const metadata =  new MapBytes({
    user_comment: "I know I'm overexposed but the price is down right now",
});
const token = new WithMetadata(2n, {
    token: new NativeToken(1n, { decimals: 18n }),
    metadata,
});

assert.strictEqual(
    metadata.toString(),
    `{"user_comment":"I know I'm overexposed but the price is down right now"}`,
);
assert.strictEqual(
    token.toString(),
    `2 WithMetadata<1 NativeToken<18>, {"user_comment":"I know I'm overexposed but the price is down right now"}>`,
);

HashedBytes

Represents bytes that are Keccak-256 hash of some other bytes. The constructor takes another Bytes object.

import { HashedBytes, MapBytes, MultisigOracle } from "@blueprintsfi/blueprints.js";
import assert from "node:assert";

const feedId = "e53dcc3bd5f13fc11bfa1907103c388b67d7b6018e4c301aa61b3cdafb1452af";

const oracle = new MultisigOracle({
    addresses: ["0xCA2421bE4AA793c9F95faef57811F4eD360f54e1"],
    threshold: 1n,
    feedId: new HashedBytes(new MapBytes({
        market: "BTC",
        condition: "above 100000",
        time: "2027-01-01 00:00:00 UTC",
    })),
});
assert.strictEqual(
    oracle.toString(),
    `MultisigOracle<[0xCA2421bE4AA793c9F95faef57811F4eD360f54e1], 1, keccak256({"market":"BTC","condition":"above 100000","time":"2027-01-01 00:00:00 UTC"})>`,
);
On this page
Defining tokensyesToken, noTokenString representationtoString()Blueprint.fromString()paramsFractionMultiplieramountweiAmountmultipliersetAmount()setWeiAmount()internalTokenId()externalTokenId()blueprintAddress()copy()BytesRawBytesStringBytesMapBytesHashedBytes