Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: trading API #98

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e227665
feat: trade api getQuote
shoom3301 Nov 19, 2024
ef4b64b
refactor: copy trading schemas from sdk
shoom3301 Nov 20, 2024
1822ead
refactor: move route path
shoom3301 Nov 20, 2024
fdbc4b0
chore: remove generateSchemas call
shoom3301 Nov 20, 2024
53868f1
chore: bump cow-sdk
shoom3301 Nov 20, 2024
e06f0cd
chore: fux build
shoom3301 Nov 20, 2024
4443bf1
chore: up sdk version
shoom3301 Nov 22, 2024
bc44fcf
fix: add getQuote error message parsing
shoom3301 Nov 22, 2024
bded4e0
feat: add orderTypedData to quote response
shoom3301 Nov 22, 2024
7a0b81b
feat(trading): add postOrder method
shoom3301 Nov 22, 2024
50de46d
feat: get decimals for trade from erc20 repo
shoom3301 Nov 26, 2024
e61b80f
feat(trading): support pre-sign order signing schema
shoom3301 Nov 26, 2024
5f5ec0f
fix(trading): return decimals for native token
shoom3301 Nov 26, 2024
9f5eeb2
feat(trading): method to get eth-flow transaction
shoom3301 Nov 26, 2024
f09ba08
chore: import util from sdk
shoom3301 Nov 26, 2024
48a9d5b
refactor: move getPreSignTransaction to sdk
shoom3301 Nov 27, 2024
4958895
chore: make NativeCurrencyDecimals map
shoom3301 Nov 27, 2024
c374485
fix(trading): define signing scheme explicitly
shoom3301 Nov 27, 2024
6117150
chore: bump sdk version
shoom3301 Nov 27, 2024
a3ca35a
chore: update schemas
shoom3301 Nov 28, 2024
81bdf8c
chore: add advancedSettings to getQuote
shoom3301 Nov 28, 2024
8ac2b37
chore: simplify trading api params
shoom3301 Nov 28, 2024
c249bce
chore: update docs
shoom3301 Nov 28, 2024
3d5a70c
Merge branch 'main' of https://github.com/cowprotocol/bff into feat/t…
shoom3301 Nov 28, 2024
15b6ec1
chore: fix build
shoom3301 Nov 28, 2024
f4bb9fc
chore: fix build
shoom3301 Nov 28, 2024
3061dda
chore: rename endpoints
shoom3301 Dec 2, 2024
af371ad
chore: rename quote endpoint
shoom3301 Dec 2, 2024
d289f14
chore: rename endpoints
shoom3301 Dec 2, 2024
40d87dc
chore: update trading README
shoom3301 Dec 3, 2024
8c71a9a
chore: update trading README
shoom3301 Dec 3, 2024
2116397
chore: update deps
shoom3301 Dec 12, 2024
694375c
Merge branch 'main' of https://github.com/cowprotocol/bff into feat/t…
shoom3301 Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions apps/api/src/app/inversify.config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import ms from 'ms';
import { PublicClient } from 'viem';
import { Container } from 'inversify';

import {
CacheRepository,
CacheRepositoryMemory,
Expand Down Expand Up @@ -27,26 +31,27 @@ import {
viemClients,
} from '@cowprotocol/repositories';

const DEFAULT_CACHE_VALUE_SECONDS = ms('2min') / 1000; // 2min cache time by default for values
const DEFAULT_CACHE_NULL_SECONDS = ms('30min') / 1000; // 30min cache time by default for NULL values (when the repository isn't known)

const CACHE_TOKEN_INFO_SECONDS = ms('24h') / 1000; // 24h

import { Container } from 'inversify';
import {
SimulationService,
SlippageService,
SlippageServiceMain,
TokenHolderService,
TokenHolderServiceMain,
TradingService,
UsdService,
UsdServiceMain,
simulationServiceSymbol,
slippageServiceSymbol,
tokenHolderServiceSymbol,
usdServiceSymbol,
tradingServiceSymbol
} from '@cowprotocol/services';
import ms from 'ms';
import { SupportedChainId } from '@cowprotocol/cow-sdk';

const DEFAULT_CACHE_VALUE_SECONDS = ms('2min') / 1000; // 2min cache time by default for values
const DEFAULT_CACHE_NULL_SECONDS = ms('30min') / 1000; // 30min cache time by default for NULL values (when the repository isn't known)

const CACHE_TOKEN_INFO_SECONDS = ms('24h') / 1000; // 24h

function getErc20Repository(cacheRepository: CacheRepository): Erc20Repository {
return new Erc20RepositoryCache(
Expand Down Expand Up @@ -133,13 +138,20 @@ function getTokenHolderRepository(
]);
}

function getTradingService(
erc20Repository: Erc20Repository
): TradingService {
return new TradingService(erc20Repository, viemClients as Record<SupportedChainId, PublicClient>);
}

function getApiContainer(): Container {
const apiContainer = new Container();
// Repositories
const cacheRepository = getCacheRepository(apiContainer);
const erc20Repository = getErc20Repository(cacheRepository);
const simulationRepository = new SimulationRepositoryTenderly();
const tokenHolderRepository = getTokenHolderRepository(cacheRepository);
const tradingService = getTradingService(erc20Repository)

apiContainer
.bind<Erc20Repository>(erc20RepositorySymbol)
Expand Down Expand Up @@ -176,6 +188,10 @@ function getApiContainer(): Container {
.bind<SimulationService>(simulationServiceSymbol)
.to(SimulationService);

apiContainer
.bind<TradingService>(tradingServiceSymbol)
.toConstantValue(tradingService);

return apiContainer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
slippageServiceSymbol,
VolatilityDetails,
} from '@cowprotocol/services';
import { ChainIdSchema, ETHEREUM_ADDRESS_PATTERN } from '../../../../schemas';
import { ChainIdSchema, ETHEREUM_ADDRESS_PATTERN, SlippageSchema } from '../../../../schemas';
import { FastifyPluginAsync } from 'fastify';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { apiContainer } from '../../../../inversify.config';
Expand Down Expand Up @@ -52,15 +52,7 @@ const successSchema = {
required: ['slippageBps'],
additionalProperties: false,
properties: {
slippageBps: {
title: 'Slippage tolerance in basis points',
description:
'Slippage tolerance in basis points. One basis point is equivalent to 0.01% (1/100th of a percent)',
type: 'number',
examples: [50, 100, 200],
minimum: 0,
maximum: 10000,
},
slippageBps: SlippageSchema,
},
} as const satisfies JSONSchema;

Expand Down
183 changes: 183 additions & 0 deletions apps/api/src/app/routes/trading/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# CoW Trading API


The **CoW Protocol** offers powerful and highly flexible trading capabilities, designed to facilitate complex decentralized finance (DeFi) interactions. However, its flexibility can also make it challenging for developers to interact [directly with the protocol](https://api.cow.fi/docs/#/default/post_api_v1_orders).

This **CoW Trading API** aims to simplify the interaction by abstracting the complexities. It automatically handles critical aspects such as parameter configuration, accurate calculations for token amounts, and signing orders.

This API functions as a wrapper around the [`@cowprotocol/cow-sdk`](https://github.com/cowprotocol/cow-sdk/blob/feat/swap-for-people/src/trading/README.md), making it easy to integrate CoW trading into your applications.

---

## Core Features
- **Simplified Order Management:** Abstracts the complexity of preparing, signing, and submitting orders.
- **Native Token Support:** Allows trading Ethereum and other native tokens seamlessly.
- **Smart-Contract Wallet Compatibility:** Includes support for wallets using EIP-1271 presigning.
- **Robust Transaction Handling:** Automatically computes necessary amounts and costs for trades.

---

## 1. **Get Quote**

The `/quote-requests` method provides a price quote for your desired trade. This includes information necessary to prepare, sign, and submit the order.

### API Endpoint
**POST** `https://bff.cow.fi/trading/quote-requests`

### Request Example

```js
fetch('https://bff.cow.fi/trading/quote-requests', {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({
trader: {
account: '0xfb3c7eb936cAA12B5A884d612393969A557d4307',
appCode: 'test1',
chainId: 1,
},
params: {
kind: 'sell',
sellToken: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
buyToken: "0xdef1ca1fb7fbcdc777520aa7f396b4e015f497ab",
amount: '12000000000000000'
}
})})
```

## 2. Full Trading Flow

This section demonstrates the complete trading flow, from retrieving a quote to signing and sending an order to the order book.

### Steps
1. **Get Quote**: Use the `/quote-requests` endpoint to retrieve the trade details.
2. **Sign Order**: Connect your wallet and sign the order using EIP-712 typed data.
3. **Submit Order**: Send the signed order to the order book.

### Code Example

```js
(async function() {
const trader = {
account: '0xfb3c7eb936cAA12B5A884d612393969A557d4307',
appCode: 'test1',
chainId: 11155111
}
const params = {
kind: 'sell',
sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
amount: '100000000000000000'
}

const callApi = (method, body) => fetch('https://bff.cow.fi/trading/' + method, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
}).then(res => res.json())

// Get quote
const { quoteResponse, orderToSign, orderTypedData, appDataInfo } = await callApi('quote-requests', { trader, params })
// Connect wallet
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' })
// Sign order
const signature = await window.ethereum.request({
method: 'eth_signTypedData_v4',
params: [accounts[0], JSON.stringify(orderTypedData)]
})
// Send order
const orderId = await callApi('orders', {
trader,
quoteId: quoteResponse.id,
signature, // Add order typed data signature (EIP-712)
orderToSign,
appDataInfo
})

console.log('Order Id:', orderId)
})()
```

## 3. Smart-Contract Wallet (EIP-1271 Pre-signing)

For smart-contract wallets, orders are signed using the [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) standard. This involves presigning the order and sending a transaction.

### Code Example

```js
(async function() {
const trader = {
account: '0xF568A3a2dfFd73C000E8E475B2D335A4A3818EBa',
appCode: 'test1',
chainId: 11155111
}
const params = {
kind: 'sell',
sellToken: '0xfff9976782d46cc05630d1f6ebab18b2324d6b14',
buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
amount: '100000000000000000'
}

const callApi = (method, body) => fetch('https://bff.cow.fi/trading/' + method, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
}).then(res => res.json())

// Get quote
const { quoteResponse, orderToSign, appDataInfo } = await callApi('quote-requests', { trader, params })
// Send order
const { orderId, preSignTransaction } = await callApi('orders', {
trader,
quoteId: quoteResponse.id,
signingScheme: 'presign', // Signal to use pre-signing (smart-contract wallet)
orderToSign,
appDataInfo
})

// ACTION NEEDED: Send <preSignTransaction> from smart-contract wallet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not adding the code to send the TX onhcain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It very depends on a smart-contract wallet implementation.
With Safe it requires a lot of code here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Safe you can go to Transaction build and put the tx data there


console.log('Order Id:', orderId)
console.log('preSignTransaction:', preSignTransaction)
})()
```

## 4. Trading Native Tokens (ETH Flow)

To trade Ethereum (native token), use the `/sell-native-currency-requests` method, which generates a transaction object for direct submission to the network.

### Code Example

```js
(async function() {
const trader = {
account: '0xfb3c7eb936cAA12B5A884d612393969A557d4307',
appCode: 'test1',
chainId: 11155111
}
const params = {
kind: 'sell',
sellToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
buyToken: '0x0625afb445c3b6b7b929342a04a22599fd5dbb59',
amount: '100000000000000000'
}

const callApi = (method, body) => fetch('https://bff.cow.fi/trading/' + method, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body)
}).then(res => res.json())

// Get quote
const { quoteResponse, orderTypedData, appDataInfo, amountsAndCosts, tradeParameters } = await callApi('quote-requests', { trader, params })
// Get transaction
const { orderId, transaction } = await callApi('sell-native-currency-requests', {
trader,
quoteId: quoteResponse.id,
tradeParameters,
amountsAndCosts,
appDataInfo,
})
// Connect wallet
const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
// Send transaction
const txHash = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [{ ...transaction, from: account }]
})

console.log('txHash:', txHash)
console.log('orderId:', orderId)
})()
```
59 changes: 59 additions & 0 deletions apps/api/src/app/routes/trading/getEthFlowTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { FastifyPluginAsync } from 'fastify';

import { FromSchema } from 'json-schema-to-ts';
import { apiContainer } from '../../inversify.config';
import {
TradingService,
tradingServiceSymbol
} from '@cowprotocol/services';

import { errorSchema, ethFlowTxBodySchema, ethFlowTxSuccessSchema } from './schemas';
import { getErrorMessage } from './utils';
import { deserializeQuoteAmountsAndCosts } from './mapQuoteAmountsAndCosts';

type SuccessSchema = FromSchema<typeof ethFlowTxSuccessSchema>;
type BodySchema = FromSchema<typeof ethFlowTxBodySchema>;
type ErrorSchema = FromSchema<typeof errorSchema>;

const tradingService: TradingService = apiContainer.get(
tradingServiceSymbol
);

const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<{
Reply: SuccessSchema | ErrorSchema;
Body: BodySchema;
}>(
'/sell-native-currency-requests',
{
schema: {
body: ethFlowTxBodySchema,
response: {
'2XX': ethFlowTxSuccessSchema,
'400': errorSchema,
},
},
},
async function (request, reply) {
const { trader, amountsAndCosts, quoteId, tradeParameters, appDataInfo } = request.body

try {
const result = await tradingService.getEthFlowTransaction(
trader,
quoteId,
tradeParameters as Parameters<typeof tradingService.getEthFlowTransaction>[2],
deserializeQuoteAmountsAndCosts(amountsAndCosts),
appDataInfo,
);

reply.send(result);
} catch (e) {
const errorMessage = getErrorMessage(e)
console.error('[Trading API] getEthFlowTransaction error', errorMessage)
reply.code(500).send({ message: errorMessage });
}
}
);
};

export default root;
Loading
Loading