Swaps

Overview

Swaps represent the simplest intent, the offer to buy/sell at a price. Swaps can be between tokens, NFTs, other swaps, or bonds. Swaps can be fulfilled directly from inventory or by engineering a route via liquidity.

The Fabriq SDK provides the IntentClient class as an API for creation, approval and submission of intents. It also provides abstractions for fetching metadata from the intentpool and on-chain to track the state of intents.

The Swaps section cover:

  • Issuing Swaps

  • Checking the Status of a Swap

  • Querying Swaps in UnEx

  • Winning Auctions for Swaps

  • Fulfilling a Swap

  • Finalizing a Swap

Initializing the Environment

For ease the guides will assume a common environment/setup:

import { IntentClient, intentPoolURL } from 'fabriq-sdk';
import { Hex, Address, maxUint256, createPublicClient, WalletClient, PublicClient} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const privateKey = /* ... */;

const walletClient = createWalletClient({
	account: privateKeyToAccount(privateKey),
  chain: hardhat, 
  transport: custom(window.ethereum) 
});

const intentClient = new IntentClient(walletClient, intentPoolURL);

Additionally, Permit2 authorization is required before the first transaction to permit :

intentClient.approvePermit2(tokenAddress: Address);

Issuing Swaps

The process of issuing a SwapIntent with the SDK is very simple. The IntentClient class provides a direct wrapper function for submitting intent messages to a GRPC intentpool such as UnEx.

Issuing a SwapIntent requires:

  • input and output addresses

  • input and output chains

  • input and output assets

import { IntentClient, getSupportedChainByID, getTestnetTokensByChainID, createItem } from '@fabriqnetwork/sdk';
import { createWalletClient, Hex, http, WalletClient, parseUnits } from 'viem';
import { optimism, sei } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';

const privateKey = /* ... */;

const walletClientOptimism: WalletClient = createWalletClient({
	account: privateKeyToAccount(privateKey),
	chain: optimism,
	transport: http(),
});

const optimismInfo = await getSupportedChainByID(optimism.id);
const tokensOptimism = await getTokensByChainID(optimism.id);

const tokensSei = await getTokensByChainID(sei.id);

const intentClient = new IntentClient(walletClientOptimism, intentPoolURL, optimismInfo);

await intentClient.approvePermit2(tokensOp.Ceres);

const inAmount = parseUnits('2.00', 18);
const outAmount = parseUnits('1.98', 18);

const input = createItem(tokensOp.Ceres, inAmount, inAmount, optimism.id);
const output = createItem(tokensSei.Ceres, outAmount, outAmount, sei.id)

const intentId = await intentClient.submitSwapIntent(input, [output]);

Once the intent has been sent to UnEx, the SDK provides a function to easily report the status of an intent:

const swap = intentClient.getSwapIntent(intentId);
console.log(swap.status);
// `pending`, `registered`, `finalized` or `expired`

This will either report that the status of the intent is pending, registered, finalized, or expired (finalized and expired intents are only kept temporarily).

Querying Swaps

Querying intents is the first step as a potential counterparty. There are a few useful querying functions such as:

  • By Origin and/or Destination

  • By Origin and/or Destination Token Types

const swaps = await intentClient.querySwaps();

// all fields in the filter interface are optional
filter = { originChainIds = [ /* ... */ ], 
           destinationChainIds = [ /* ... */ ],
           originTokens = [ /* ... */ ],
           destinationTokens = [ /* ... */ ]
         }

const filteredSwaps = await intentClient.querySwaps(filter);

The following example gets all intents originating from Optimism and destined for Sei, using the filter interface

op_filter = { originChainIds = [ optimism.id ], 
		          destinationChainIds = [ sei.id ]
		        }

const filteredSwaps = await intentClient.querySwaps(op_filter);

Auction and Registration On-chain

To promote optimal routing, all potential counterparties participate in a dutch auction competing to fulfill swap intents. The first to register the intent on-chain and commit is the winner. The counterparty will need to register it on-chain.

This example first filters the intents by chain, uses a

const filteredSwaps = await intentClient.getSwaps(filter);

const desirableSwaps = selectSwaps(filteredSwaps);

var wonSwaps = []

for (const swap of desirableSwaps) {
	const response = await intentClient.registerSwapIntent(swapIntent);
	if (response.ok) {
		wonIntents.push(swapIntent);
	}
}

Fulfilling a SwapIntent and Finalization

The SDK currently does not support fulfillment from the APIs it provides. For this example, we complete fulfillment with viem provided APIs.

// get a Swap as an example
const swap = wonSwaps[0];

// Sender's private key
const fillerPrivateKey = /* ... */;

async function transferExampleToken() {
  const walletClient = createWalletClient({
    account: privateKeyToAccount(fillerPrivateKey),
    chain: sei.id,
    transport: http(sei.url),
  });

	const txHash = await walletClient.writeContract({ 
		abi: ERC20_ABI, 
		address: exampleTokenAddress, 
		functionName: 'transfer', 
		args: [swap.destinationAddress, swap.destinationAmount] });

  console.log('Transaction Hash:', txHash);
  
  // finalize
}

transferExampleToken().catch(console.error);

The last step in a swaps lifecycle, finalization, which releases the input tokens to the filler, is handled automatically by the oracle system. No interaction from the SDK is needed at this point.

Last updated