A JSON based language for defining Brink intents

The Brink DSL lets you define intents in JSON. It provides a simple interface on top of the function call data that all intents are composed of.

🚧

Warning

Much like writing and deploying your own smart contract code on an EVM network, intents built with the Brink DSL can contain bugs. You as a developer are responsible for the security of the intents you build.

If you prompt a user of an application to sign an intent that has been defined with the DSL, you are asking them to permit 3rd party solvers to run a series of smart contract calls on their behalf. Improper construction of intents can lead to unintended outcomes, un-executable actions, sub-optimal swaps, or fund loss.

Intent

An intent represents a series of conditions and actions that a signer wishes to delegate to a solver. Every condition and action defined in the intent needs to be run by the solver in a single transaction, in order for the solver to successfully execute.

An intent can define replay rules for solver execution, an expiry block, conditions required, and actions to execute. Here is an example of an intent for a swap of 1.5 WETH to DAI at market price that will only execute when the WETH/DAI price is less than 1,400 as reported by a Uniswap V3 TWAP

{
  chainId: 1,
  replay: {
    nonce: 555,
    runs: 'ONCE'
  },
  expiryBlock: 21_000_000,
  conditions: [{
    type: 'price',
    operator: 'lt',
    tokenA: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
    tokenB: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
    price: 1400.00
  }],
  actions: [{
    type: 'marketSwap',
    tokenIn: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
    tokenOut: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
    tokenInAmount: 1_500000000000000000
  }]
}

chainId

The chainId of the network where this intent will be executed.

replay

The rules for intent replay protection. Intents can be required to run only once, as would be the case for most limit swaps. Intents can also be run any number of times until cancelled.

expiryBlock

If expiryBlock is set, the intent will be un-executable in or after the provided block.

conditions

An array of conditions that solvers are required to run before executing actions. When a solver submits a transaction to solve an intent, all conditions in the intent must not revert.

actions

An array of actions that must be run in order to successfully execute a transaction for the intent. Actions define state outcomes that must be met by solvers.

Declaration of Multiple Intents

Multiple intents can be defined as a Declaration and signed together with a single signature. This lets users sign multiple actions that solvers must run separately.

Here is an example of a Declaration with two intents. These intents represent two completely different limit swaps. The intents could be run by different solvers, in separate transactions, or even in two separate blocks:

[
  {
    chainId: 1,
    replay: {
      nonce: 1,
      runs: 'ONCE'
    },
    actions: [{
      type: 'limitSwap',
      tokenIn: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
      tokenOut: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // DAI
      tokenInAmount: 1_000000000000000000,
      tokenOutAmount: 2200_000000000000000000
    }]
  },
  {
    chainId: 1,
    replay: {
      nonce: 2,
      runs: 'ONCE'
    },
    actions: [{
      type: 'limitSwap',
      tokenIn: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', // WBTC
      tokenOut: '0x6B175474E89094C44Da98b954EedeAC495271d0F', //DAI
      tokenInAmount: 1_000000000000000000,
      tokenOutAmount: 40000_000000000000000000
    }]
  }
]

Sequential Intents

Intents can be forced to run sequential using the nonce condition. In this example, the first intent has to be successfully executed in order for the second to be run:

[
  {
    chainId: 1,
    replay: {
      nonce: 1,
      runs: 'ONCE'
    },
    actions: [...]
  },
  {
    chainId: 1,
    replay: {
      nonce: 2,
      runs: 'ONCE'
    },
    conditions: [{
    	type: 'nonce',
      nonce: 1,
      state: 'USED'
    }],
    actions: [...]
  }
]

Exclusive Intents

Intents that share the same replay nonce are mutually exclusive. In this example, only one of these intents can ever be executed. Executing one effectively cancels the others:

[
  {
    chainId: 1,
    replay: {
      nonce: 5,
      runs: 'ONCE'
    },
    actions: [...]
  },
  {
    chainId: 1,
    replay: {
      nonce: 5,
      runs: 'ONCE'
    },
    actions: [...]
  },
  {
    chainId: 1,
    replay: {
      nonce: 5,
      runs: 'ONCE'
    },
    actions: [...]
  }
]

Compiling Intents for Signature

Intents defined using the Brink DSL need to be transformed to raw call data in order to be signed. The easiest way to do this is by using the /intents/compile API endpoint.

Calling /intents/compilewith an intent, or with a declaration array of multiple intents, will return EIP-712 typed data to sign. Signer address and signature type must also be provided.

curl --request GET \
     --url `https://api.brink.trade/intents/compile/v1?
              signer=0x47A3D46d7c41eaE1238db631C7a97fB66a62d304&
              signatureType=EIP712&
              declaration=<INTENT_OR_DECLARATION_JSON> \
     --header 'x-api-key: <YOUR_API_KEY>' \
     --header 'accept: application/json'