Skip to content

Commit

Permalink
feat: js example for trade-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 committed Jun 27, 2024
1 parent 59f550b commit 6fab215
Show file tree
Hide file tree
Showing 21 changed files with 726 additions and 558 deletions.
6 changes: 3 additions & 3 deletions examples/vanilla/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
"start": "webpack serve --mode=development",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"@cowprotocol/cow-sdk": "^4.0.3"
},
"dependencies": {},
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.5.0",
"style-loader": "^4.0.0",
"ts-loader": "^9.4.2",
"webpack": "^5.76.3",
"webpack-cli": "^5.0.1",
Expand Down
50 changes: 50 additions & 0 deletions examples/vanilla/src/formState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { OrderKind, SupportedChainId, SwapParameters } from '../../../src'
import { TOKENS } from './tokens'

const appCode = 'trade-sdk-example'

interface FormState {
privateKey: string
chainId: string
sellToken: string
buyToken: string
amount: string
slippageBps: string
kind: 'sell' | 'buy'
}

export const getFormState = (): FormState => {
return Object.fromEntries(new FormData(document.getElementById('form') as HTMLFormElement)) as unknown as FormState
}

export const getSwapParameters = (): SwapParameters => {
const {
privateKey,
slippageBps: _slippageBps,
chainId: _chainId,
sellToken: _sellToken,
buyToken: _buyToken,
amount: _amount,
kind,
} = getFormState()

const chainId: SupportedChainId = +_chainId
const isSell = kind === 'sell'
const sellToken = TOKENS[chainId].find((t) => t.address === _sellToken)
const buyToken = TOKENS[chainId].find((t) => t.address === _buyToken)
const amount = BigInt(_amount) * BigInt(10 ** (isSell ? sellToken.decimals : buyToken.decimals))
const slippageBps = _slippageBps ? +_slippageBps : undefined

return {
appCode,
signer: privateKey,
chainId,
sellToken: sellToken.address,
sellTokenDecimals: sellToken.decimals,
buyToken: buyToken.address,
buyTokenDecimals: buyToken.decimals,
amount: amount.toString(),
slippageBps,
kind: isSell ? OrderKind.SELL : OrderKind.BUY,
}
}
88 changes: 77 additions & 11 deletions examples/vanilla/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,84 @@
import { OrderBookApi, SubgraphApi, SupportedChainId } from '@cowprotocol/cow-sdk'
import { SupportedChainId, getQuote, UnsignedOrder } from '../../../src'

// See more examples in /examples/cra
import { getOrderToSignFromQuoteResult, swapParamsToLimitOrderParams, postCoWProtocolTrade } from '../../../src/trading'

import { TOKENS } from './tokens'
import { atomsToAmount } from './utils'
import { pageHtml } from './pageHtml'
import { pageActions, printResult } from './pageActions'
import { GetQuoteResult } from '../../../src/trading/getQuote'
import { getFormState, getSwapParameters } from './formState'
import './styles.css'

// Run the example
;(async function () {
const orderBookApi = new OrderBookApi({ chainId: SupportedChainId.MAINNET })
let getQuoteResult: GetQuoteResult | null = null

// Render page
const page = pageHtml()
document.body.appendChild(page)

// Bind actions to the page
pageActions({
onFormReset() {
getQuoteResult = null
},
async onGetQuote() {
const {
slippageBps: _slippageBps,
chainId: _chainId,
sellToken: _sellToken,
buyToken: _buyToken,
amount: _amount,
kind,
} = getFormState()

const chainId: SupportedChainId = +_chainId
const isSell = kind === 'sell'
const sellToken = TOKENS[chainId].find((t) => t.address === _sellToken)
const buyToken = TOKENS[chainId].find((t) => t.address === _buyToken)

getQuoteResult = await getQuote(getSwapParameters())

const {
amountsAndCosts: { beforeNetworkCosts, afterSlippage },
} = getQuoteResult

console.log('Quote results:', getQuoteResult)

const outputToken = isSell ? buyToken : sellToken

const order = await orderBookApi.getOrder(
'0xff2e2e54d178997f173266817c1e9ed6fee1a1aae4b43971c53b543cffcc2969845c6f5599fbb25dbdd1b9b013daf85c03f3c63763e4bc4a'
)
printResult(`
Quote amount: ${atomsToAmount(
beforeNetworkCosts[isSell ? 'buyAmount' : 'sellAmount'],
outputToken.decimals
)} ${outputToken.symbol}
Amount to sign: ${atomsToAmount(
afterSlippage[isSell ? 'buyAmount' : 'sellAmount'],
outputToken.decimals
)} ${outputToken.symbol}
See more info in the console (Quote results)
`)
},
async onConfirmOrder() {
const orderToSign = await getOrderToSignFromQuoteResult(getQuoteResult, getSwapParameters())

const orderElement = document.createElement('textarea')
printResult(`
You are going to sign:
${JSON.stringify(orderToSign, null, 4)}
`)
},
async onSignAndSendOrder() {
const { orderBookApi, signer, appDataInfo, quoteResponse } = getQuoteResult

orderElement.value = JSON.stringify({ order }, null, 4)
orderElement.style.minWidth = '950px'
orderElement.style.minHeight = '800px'
const orderId = await postCoWProtocolTrade(
orderBookApi,
signer,
appDataInfo,
swapParamsToLimitOrderParams(getSwapParameters(), quoteResponse)
)

document.body.appendChild(orderElement)
printResult(`Order created, id: ${orderId}`)
},
})
})()
96 changes: 96 additions & 0 deletions examples/vanilla/src/pageActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { tokensSelect } from './pageHtml'

const sendOrderText = 'Send order'

interface Actions {
onFormReset(): void
onGetQuote(): Promise<void>
onConfirmOrder(): Promise<void>
onSignAndSendOrder(): Promise<void>
}

export function pageActions(actions: Actions) {
onFormReset(actions.onFormReset)
onNetworkChange()
onGetQuote(actions)
}

export function printResult(text: string) {
const resultsEl = document.getElementById('results')! as HTMLTextAreaElement

resultsEl.value = text
.split('\n')
.map((t) => t.trim())
.join('\n')
}

function onFormReset(callback: () => void) {
document.getElementById('form')?.addEventListener('change', () => {
printResult('')

document.getElementById('sendOrder').innerText = sendOrderText
callback()
})
}

function onNetworkChange() {
document.getElementById('chainId').addEventListener('change', (event) => {
const chainId = +(event.target as unknown as { value: string }).value

document.getElementById('sellToken')!.parentElement.innerHTML = tokensSelect(chainId, 'sellToken')
document.getElementById('buyToken')!.parentElement.innerHTML = tokensSelect(chainId, 'buyToken')
})
}

function onGetQuote(actions: Actions) {
document.getElementById('getQuote').addEventListener('click', (event) => {
event.preventDefault()

printResult('Loading...')

const sendOrderEl = document.getElementById('sendOrder')

sendOrderEl.innerText = sendOrderText

actions.onFormReset()

actions
.onGetQuote()
.then(() => {
const sendOrderEl = document.getElementById('sendOrder') as HTMLButtonElement

sendOrderEl.style.display = 'inline-block'

sendOrderEl.addEventListener('click', async (event) => {
event.preventDefault()

sendOrderEl.disabled = true
sendOrderEl.innerText = 'Loading...'

try {
if (sendOrderEl.innerText === sendOrderText) {
await actions.onConfirmOrder()

sendOrderEl.innerText = 'Sign and send'
} else {
await actions.onSignAndSendOrder()

sendOrderEl.innerText = sendOrderText
sendOrderEl.style.display = 'none'
}
} catch (error) {
printError(error)
} finally {
sendOrderEl.disabled = false
}
})
})
.catch((error) => {
printError(error)
})
})
}

function printError(error: any) {
printResult(JSON.stringify(error.body || error.message || error.toString(), null, 4))
}
81 changes: 81 additions & 0 deletions examples/vanilla/src/pageHtml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { ALL_SUPPORTED_CHAIN_IDS, SupportedChainId } from '../../../src'
import { TOKENS } from './tokens'

const chainId = SupportedChainId.MAINNET

export const tokensSelect = (chainId: SupportedChainId, name: string) =>
`<select name="${name}" id="${name}">
${TOKENS[chainId].map((token) => `<option value="${token.address}">${token.symbol}</option>`)}
</select>`

export function pageHtml() {
const page = document.createElement('div')

const networksSelect = () =>
`<select name="chainId" id="chainId">
${ALL_SUPPORTED_CHAIN_IDS.map((chainId) => `<option value="${chainId}">${chainId}</option>`)}
</select>`

page.innerHTML = `
<table id="layout">
<tr>
<td>
<form id="form">
<h3>Swap</h3>
<div>
<label for="privateKey">Private key</label>
<div><input name="privateKey"/></div>
</div>
<div>
<label for="chainId">Network</label>
<div>${networksSelect()}</div>
</div>
<div>
<label for="sellToken">Sell token</label>
<div>${tokensSelect(chainId, 'sellToken')}</div>
</div>
<div>
<label for="sellToken">Buy token</label>
<div>${tokensSelect(chainId, 'buyToken')}</div>
</div>
<div>
<label for="amount">Amount</label>
<div><input name="amount" value="20"/></div>
</div>
<div>
<label for="slippageBps">Slippage (BPS)</label>
<div><input name="slippageBps" type="number"/></div>
</div>
<div>
<label for="kind">Type</label>
<div>
<select name="kind">
<option value="sell">Sell</option>
<option value="buy">Buy</option>
</select>
</div>
</div>
<div>
<button id="getQuote">Get quote</button>
<button id="sendOrder">Send order</button>
</div>
</form>
</td>
<td>
<h4>Result:</h4>
<textarea id="results" disabled></textarea>
</td>
</tr>
</table>
`

return page
}
41 changes: 41 additions & 0 deletions examples/vanilla/src/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#layout {
margin: 0 auto;
}

#layout td {
vertical-align: top;
}

#form {
min-width: 400px;
}

#form > div {
border: 1px solid #d4d9de;
margin: 10px;
padding: 10px;
}

#form input, #form select {
width: 100%;
box-sizing: border-box;
}

#form button {
background: #282c34;
color: #fff;
font-size: 18px;
padding: 5px 10px;
cursor: pointer;
border: 0;
border-radius: 4px;
}

#results {
min-height: 400px;
min-width: 600px;
}

#sendOrder {
display: none;
}
Loading

0 comments on commit 6fab215

Please sign in to comment.