├── .gitignore ├── .idea ├── .gitignore ├── AlgoSwap.iml ├── misc.xml ├── modules.xml └── vcs.xml ├── LICENSE.txt ├── Makefile ├── README.md ├── algoswap_logo.png ├── build ├── escrow.teal ├── escrow_logicsig ├── manager_approval.teal ├── manager_clear.teal ├── validator_approval.teal └── validator_clear.teal ├── config.sh ├── contracts ├── escrow.py ├── manager.py ├── test.py └── validator.py ├── deploy.py ├── docs └── protocol.md ├── frontend ├── .editorconfig ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.json ├── README.md ├── package.json ├── public │ ├── algosigner.png │ ├── favicon.ico │ ├── index.html │ ├── logo.png │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── settings.png ├── src │ ├── App.scss │ ├── App.test.tsx │ ├── App.tsx │ ├── assets │ │ ├── fonts │ │ │ └── 4 │ │ │ │ ├── Overpass-Regular.woff │ │ │ │ └── Overpass-Regular.woff2 │ │ └── images │ │ │ └── algosigner.png │ ├── components │ │ ├── AddLiquidity.scss │ │ ├── AddLiquidity.tsx │ │ ├── CreatePair.scss │ │ ├── CreatePair.tsx │ │ ├── CreatePairModal.scss │ │ ├── CreatePairModal.tsx │ │ ├── LiquidityList.css │ │ ├── LiquidityList.tsx │ │ ├── NavigationBar.scss │ │ ├── NavigationBar.tsx │ │ ├── SwapComponent │ │ │ ├── SwapComponent.scss │ │ │ ├── SwapComponent.tsx │ │ │ ├── SwapModal.scss │ │ │ └── SwapModal.tsx │ │ ├── TokenAmount │ │ │ ├── TokenAmount.scss │ │ │ ├── TokenAmount.tsx │ │ │ ├── TokenModal.scss │ │ │ └── TokenModal.tsx │ │ └── common │ │ │ ├── SettingsModal.scss │ │ │ ├── SettingsModal.tsx │ │ │ ├── WalletModal.scss │ │ │ └── WalletModal.tsx │ ├── config.js │ ├── config │ │ ├── development.js │ │ └── production.js │ ├── constants.ts │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── models │ │ └── PairDetails.ts │ ├── pages │ │ ├── AddPage.tsx │ │ ├── CreatePage.tsx │ │ ├── PoolPage.scss │ │ ├── PoolPage.tsx │ │ └── SwapPage.tsx │ ├── react-app-env.d.ts │ ├── redux │ │ ├── actions │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── reducers │ │ │ ├── index.ts │ │ │ ├── tokens.ts │ │ │ ├── transaction.ts │ │ │ ├── types.ts │ │ │ └── user.ts │ ├── serviceWorker.ts │ ├── services │ │ ├── addLiquidity.ts │ │ ├── constants.ts │ │ ├── helpers.ts │ │ ├── swapToken1ForToken2.ts │ │ ├── swapToken2ForToken1.ts │ │ └── withdrawLiquidity.ts │ └── setupTests.ts ├── tsconfig.json └── yarn.lock ├── requirements.txt ├── scripts ├── dump_add_liquidity_txns.sh ├── dump_swap_t1_for_t2_txns.sh ├── dump_swap_t2_for_t1_txns.sh └── dump_withdraw_liquidity_txns.sh └── tests ├── add_liquidity_test.py ├── helpers.py ├── swap_t1_for_t2_test.py ├── swap_t2_for_t1_test.py ├── withdraw_liquidity_test.py └── withdraw_protocol_fees_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .mypy_cache/ 3 | __pycache__/ 4 | contracts/__pycache__/ 5 | settings.json 6 | .DS_Store 7 | *.code-workspace -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/AlgoSwap.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Hypotenuse Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | deploy: venv/bin/activate 2 | . ./config.sh; . venv/bin/activate; python3 deploy.py 3 | 4 | add_liq_test: venv/bin/activate 5 | . ./config.sh; . venv/bin/activate; python3 tests/add_liquidity_test.py 6 | 7 | swap_t1_for_t2_test: venv/bin/activate 8 | . ./config.sh; . venv/bin/activate; python3 tests/swap_t1_for_t2_test.py 9 | 10 | swap_t2_for_t1_test: venv/bin/activate 11 | . ./config.sh; . venv/bin/activate; python3 tests/swap_t2_for_t1_test.py 12 | 13 | withdraw_liq_test: venv/bin/activate 14 | . ./config.sh; . venv/bin/activate; python3 tests/withdraw_liquidity_test.py 15 | 16 | withdraw_protocol_fees_test: venv/bin/activate 17 | . ./config.sh; . venv/bin/activate; python3 tests/withdraw_protocol_fees_test.py 18 | 19 | venv/bin/activate: requirements.txt 20 | rm -rf venv; python3 -m venv venv; . venv/bin/activate; python3 -m pip install -r requirements.txt 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AlgoSwap 2 | 3 | # WARNING: THIS CODE HAS NOT BEEN AUDITED AND SHOULD NOT BE USED ON THE ALGORAND MAINNET - USE AT YOUR OWN RISK! 4 | 5 | ![AlgoSwap Logo](/algoswap_logo.png) 6 | 7 | 8 | ## Overview 9 | 10 | AlgoSwap is an automated market maker like UniSwap built on the Pure Proof-of-Stake Algorand Blockchain. It relies on the `xy = k` function to maintain exchange rates for liquidity pairs in the market. 11 | 12 | --- 13 | 14 | ## Protocol 15 | 16 | See the [AlgoSwap Protocol](./docs/protocol.md) documentation. 17 | 18 | --- 19 | 20 | ## Setup and Initialization 21 | 22 | TODO 23 | 24 | --- 25 | 26 | ## TODO 27 | 28 | - Test cases 29 | - Anti-frontrunning using hashed commitments: in one block you must commit to making a trade by putting down a deposit (the amount needs to be thought out to properly align incentives), in a later block you would follow through on the commitment by actually making a trade, you can't make a trade unless you've committed to it in a previous block, this should entirely prevent frontrunning bots 30 | 31 | ## Credits 32 | 33 | AlgoSwap was made possible by a [generous grant from the Algorand Foundation](https://algorand.foundation/grants-program/2020-grant-recipients), and the hard work of the following individuals: 34 | 35 | - [calvinchan1](https://github.com/calvinchan1) 36 | - [ehliang](https://github.com/ehliang) 37 | - [haardikk21](https://github.com/haardikk21) 38 | - [luoyang9](https://github.com/luoyang9) 39 | - [mattreyes](https://github.com/mattreyes) 40 | - [uberi](https://github.com/uberi) 41 | - [windforcer](https://github.com/windforcer) 42 | 43 | ## License 44 | 45 | AlgoSwap is made available under the [MIT license](./LICENSE.txt). 46 | -------------------------------------------------------------------------------- /algoswap_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/algoswap_logo.png -------------------------------------------------------------------------------- /build/escrow.teal: -------------------------------------------------------------------------------- 1 | #pragma version 2 2 | global GroupSize 3 | int 1 4 | == 5 | bnz l6 6 | global GroupSize 7 | int 3 8 | == 9 | bnz l5 10 | global GroupSize 11 | int 4 12 | == 13 | bnz l4 14 | err 15 | l4: 16 | gtxn 0 TypeEnum 17 | int appl 18 | == 19 | gtxn 0 ApplicationID 20 | int 14973862 21 | == 22 | && 23 | gtxn 1 TypeEnum 24 | int appl 25 | == 26 | && 27 | gtxn 1 ApplicationID 28 | int 14973861 29 | == 30 | && 31 | txn GroupIndex 32 | int 2 33 | == 34 | txn GroupIndex 35 | int 3 36 | == 37 | || 38 | && 39 | txn TypeEnum 40 | int axfer 41 | == 42 | && 43 | txn CloseRemainderTo 44 | global ZeroAddress 45 | == 46 | && 47 | txn AssetCloseTo 48 | global ZeroAddress 49 | == 50 | && 51 | b l7 52 | l5: 53 | gtxn 0 TypeEnum 54 | int appl 55 | == 56 | gtxn 0 ApplicationID 57 | int 14973862 58 | == 59 | && 60 | gtxn 1 TypeEnum 61 | int appl 62 | == 63 | && 64 | gtxn 1 ApplicationID 65 | int 14973861 66 | == 67 | && 68 | txn GroupIndex 69 | int 2 70 | == 71 | && 72 | txn TypeEnum 73 | int axfer 74 | == 75 | && 76 | txn CloseRemainderTo 77 | global ZeroAddress 78 | == 79 | && 80 | txn AssetCloseTo 81 | global ZeroAddress 82 | == 83 | && 84 | b l7 85 | l6: 86 | txn OnCompletion 87 | int OptIn 88 | == 89 | txn LastValid 90 | int 13115618 91 | <= 92 | && 93 | txn ApplicationID 94 | int 14973862 95 | == 96 | txn ApplicationID 97 | int 14973861 98 | == 99 | || 100 | && 101 | txn TypeEnum 102 | int axfer 103 | == 104 | txn Sender 105 | txn AssetReceiver 106 | == 107 | && 108 | txn LastValid 109 | int 13115618 110 | <= 111 | && 112 | txn XferAsset 113 | int 14973863 114 | == 115 | txn XferAsset 116 | int 14973864 117 | == 118 | || 119 | txn XferAsset 120 | int 14973866 121 | == 122 | || 123 | && 124 | || 125 | l7: -------------------------------------------------------------------------------- /build/escrow_logicsig: -------------------------------------------------------------------------------- 1 | AiALAQMEBqb3kQel95EHAuLBoAan95EHqPeRB6r3kQcyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE= -------------------------------------------------------------------------------- /build/manager_approval.teal: -------------------------------------------------------------------------------- 1 | #pragma version 2 2 | txn ApplicationID 3 | int 0 4 | == 5 | bnz l20 6 | txn OnCompletion 7 | int CloseOut 8 | == 9 | bnz l20 10 | txn OnCompletion 11 | int OptIn 12 | == 13 | bnz l42 14 | txna ApplicationArgs 0 15 | byte "s1" 16 | == 17 | bnz l39 18 | txna ApplicationArgs 0 19 | byte "s2" 20 | == 21 | bnz l36 22 | txna ApplicationArgs 0 23 | byte "a" 24 | == 25 | bnz l24 26 | txna ApplicationArgs 0 27 | byte "w" 28 | == 29 | bnz l21 30 | txna ApplicationArgs 0 31 | byte "r" 32 | == 33 | bnz l13 34 | txna ApplicationArgs 0 35 | byte "p" 36 | == 37 | bnz l10 38 | err 39 | l10: 40 | gtxn 2 AssetAmount 41 | int 1 42 | byte "P1" 43 | app_local_get 44 | <= 45 | gtxn 3 AssetAmount 46 | int 1 47 | byte "P2" 48 | app_local_get 49 | <= 50 | && 51 | bnz l12 52 | err 53 | l12: 54 | int 1 55 | byte "P1" 56 | int 1 57 | byte "P1" 58 | app_local_get 59 | gtxn 2 AssetAmount 60 | - 61 | app_local_put 62 | int 1 63 | byte "P2" 64 | int 1 65 | byte "P2" 66 | app_local_get 67 | gtxn 3 AssetAmount 68 | - 69 | app_local_put 70 | int 1 71 | b l48 72 | l13: 73 | gtxn 2 XferAsset 74 | int 1 75 | byte "T1" 76 | app_local_get 77 | == 78 | gtxn 2 AssetAmount 79 | int 0 80 | byte "U1" 81 | txna Accounts 1 82 | concat 83 | app_local_get 84 | <= 85 | && 86 | bnz l19 87 | gtxn 2 XferAsset 88 | int 1 89 | byte "T2" 90 | app_local_get 91 | == 92 | gtxn 2 AssetAmount 93 | int 0 94 | byte "U2" 95 | txna Accounts 1 96 | concat 97 | app_local_get 98 | <= 99 | && 100 | bnz l18 101 | gtxn 2 XferAsset 102 | int 1 103 | byte "LT" 104 | app_local_get 105 | == 106 | gtxn 2 AssetAmount 107 | int 0 108 | byte "UL" 109 | txna Accounts 1 110 | concat 111 | app_local_get 112 | <= 113 | && 114 | bnz l17 115 | err 116 | l17: 117 | int 0 118 | byte "UL" 119 | txna Accounts 1 120 | concat 121 | int 0 122 | byte "UL" 123 | txna Accounts 1 124 | concat 125 | app_local_get 126 | gtxn 2 AssetAmount 127 | - 128 | app_local_put 129 | b l20 130 | l18: 131 | int 0 132 | byte "U2" 133 | txna Accounts 1 134 | concat 135 | int 0 136 | byte "U2" 137 | txna Accounts 1 138 | concat 139 | app_local_get 140 | gtxn 2 AssetAmount 141 | - 142 | app_local_put 143 | b l20 144 | l19: 145 | int 0 146 | byte "U1" 147 | txna Accounts 1 148 | concat 149 | int 0 150 | byte "U1" 151 | txna Accounts 1 152 | concat 153 | app_local_get 154 | gtxn 2 AssetAmount 155 | - 156 | app_local_put 157 | l20: 158 | int 1 159 | b l48 160 | l21: 161 | int 1 162 | byte "B1" 163 | app_local_get 164 | gtxn 2 AssetAmount 165 | * 166 | int 1 167 | byte "LD" 168 | app_local_get 169 | / 170 | store 7 171 | int 0 172 | byte "U1" 173 | txna Accounts 1 174 | concat 175 | int 0 176 | byte "U1" 177 | txna Accounts 1 178 | concat 179 | app_local_get 180 | load 7 181 | + 182 | app_local_put 183 | int 1 184 | byte "B2" 185 | app_local_get 186 | gtxn 2 AssetAmount 187 | * 188 | int 1 189 | byte "LD" 190 | app_local_get 191 | / 192 | store 8 193 | int 0 194 | byte "U2" 195 | txna Accounts 1 196 | concat 197 | int 0 198 | byte "U2" 199 | txna Accounts 1 200 | concat 201 | app_local_get 202 | load 8 203 | + 204 | app_local_put 205 | load 7 206 | txna ApplicationArgs 1 207 | btoi 208 | >= 209 | load 8 210 | txna ApplicationArgs 2 211 | btoi 212 | >= 213 | && 214 | bnz l23 215 | err 216 | l23: 217 | int 1 218 | byte "B1" 219 | int 1 220 | byte "B1" 221 | app_local_get 222 | load 7 223 | - 224 | app_local_put 225 | int 1 226 | byte "B2" 227 | int 1 228 | byte "B2" 229 | app_local_get 230 | load 8 231 | - 232 | app_local_put 233 | int 1 234 | byte "LD" 235 | int 1 236 | byte "LD" 237 | app_local_get 238 | gtxn 2 AssetAmount 239 | - 240 | app_local_put 241 | int 1 242 | b l48 243 | l24: 244 | int 1 245 | byte "B1" 246 | app_local_get 247 | store 2 248 | int 1 249 | byte "B2" 250 | app_local_get 251 | store 3 252 | int 1 253 | byte "LD" 254 | app_local_get 255 | store 4 256 | load 4 257 | int 0 258 | == 259 | bnz l32 260 | gtxn 3 AssetAmount 261 | load 2 262 | * 263 | load 3 264 | / 265 | store 10 266 | gtxn 2 AssetAmount 267 | load 10 268 | < 269 | bnz l27 270 | load 10 271 | store 0 272 | b l28 273 | l27: 274 | gtxn 2 AssetAmount 275 | store 0 276 | l28: 277 | gtxn 2 AssetAmount 278 | load 3 279 | * 280 | load 2 281 | / 282 | store 10 283 | gtxn 3 AssetAmount 284 | load 10 285 | < 286 | bnz l30 287 | load 10 288 | store 1 289 | b l31 290 | l30: 291 | gtxn 3 AssetAmount 292 | store 1 293 | l31: 294 | load 4 295 | gtxn 2 AssetAmount 296 | * 297 | load 2 298 | / 299 | store 9 300 | b l33 301 | l32: 302 | gtxn 2 AssetAmount 303 | store 0 304 | gtxn 3 AssetAmount 305 | store 1 306 | gtxn 2 AssetAmount 307 | store 9 308 | l33: 309 | load 9 310 | txna ApplicationArgs 1 311 | btoi 312 | >= 313 | bnz l35 314 | err 315 | l35: 316 | int 0 317 | byte "U1" 318 | txna Accounts 1 319 | concat 320 | int 0 321 | byte "U1" 322 | txna Accounts 1 323 | concat 324 | app_local_get 325 | gtxn 2 AssetAmount 326 | + 327 | load 0 328 | - 329 | app_local_put 330 | int 0 331 | byte "U2" 332 | txna Accounts 1 333 | concat 334 | int 0 335 | byte "U2" 336 | txna Accounts 1 337 | concat 338 | app_local_get 339 | gtxn 3 AssetAmount 340 | + 341 | load 1 342 | - 343 | app_local_put 344 | int 0 345 | byte "UL" 346 | txna Accounts 1 347 | concat 348 | int 0 349 | byte "UL" 350 | txna Accounts 1 351 | concat 352 | app_local_get 353 | load 9 354 | + 355 | app_local_put 356 | int 1 357 | byte "B1" 358 | load 2 359 | load 0 360 | + 361 | app_local_put 362 | int 1 363 | byte "B2" 364 | load 3 365 | load 1 366 | + 367 | app_local_put 368 | int 1 369 | byte "LD" 370 | load 4 371 | load 9 372 | + 373 | app_local_put 374 | int 1 375 | b l48 376 | l36: 377 | int 1 378 | byte "B1" 379 | app_local_get 380 | int 1 381 | byte "B1" 382 | app_local_get 383 | int 1 384 | byte "B2" 385 | app_local_get 386 | * 387 | int 1 388 | byte "B2" 389 | app_local_get 390 | gtxn 2 AssetAmount 391 | int 199 392 | * 393 | int 200 394 | / 395 | + 396 | / 397 | - 398 | store 6 399 | int 1 400 | byte "P2" 401 | int 1 402 | byte "P2" 403 | app_local_get 404 | gtxn 2 AssetAmount 405 | int 2000 406 | / 407 | + 408 | app_local_put 409 | load 6 410 | txna ApplicationArgs 1 411 | btoi 412 | >= 413 | bnz l38 414 | err 415 | l38: 416 | int 0 417 | byte "U1" 418 | txna Accounts 1 419 | concat 420 | int 0 421 | byte "U1" 422 | txna Accounts 1 423 | concat 424 | app_local_get 425 | load 6 426 | + 427 | app_local_put 428 | int 1 429 | byte "B2" 430 | int 1 431 | byte "B2" 432 | app_local_get 433 | gtxn 2 AssetAmount 434 | int 9 435 | * 436 | int 2000 437 | / 438 | + 439 | gtxn 2 AssetAmount 440 | int 199 441 | * 442 | int 200 443 | / 444 | + 445 | app_local_put 446 | int 1 447 | byte "B1" 448 | int 1 449 | byte "B1" 450 | app_local_get 451 | load 6 452 | - 453 | app_local_put 454 | int 1 455 | b l48 456 | l39: 457 | int 1 458 | byte "B2" 459 | app_local_get 460 | int 1 461 | byte "B1" 462 | app_local_get 463 | int 1 464 | byte "B2" 465 | app_local_get 466 | * 467 | int 1 468 | byte "B1" 469 | app_local_get 470 | gtxn 2 AssetAmount 471 | int 199 472 | * 473 | int 200 474 | / 475 | + 476 | / 477 | - 478 | store 5 479 | int 1 480 | byte "P1" 481 | int 1 482 | byte "P1" 483 | app_local_get 484 | gtxn 2 AssetAmount 485 | int 2000 486 | / 487 | + 488 | app_local_put 489 | load 5 490 | txna ApplicationArgs 1 491 | btoi 492 | >= 493 | bnz l41 494 | err 495 | l41: 496 | int 0 497 | byte "U2" 498 | txna Accounts 1 499 | concat 500 | int 0 501 | byte "U2" 502 | txna Accounts 1 503 | concat 504 | app_local_get 505 | load 5 506 | + 507 | app_local_put 508 | int 1 509 | byte "B1" 510 | int 1 511 | byte "B1" 512 | app_local_get 513 | gtxn 2 AssetAmount 514 | int 9 515 | * 516 | int 2000 517 | / 518 | + 519 | gtxn 2 AssetAmount 520 | int 199 521 | * 522 | int 200 523 | / 524 | + 525 | app_local_put 526 | int 1 527 | byte "B2" 528 | int 1 529 | byte "B2" 530 | app_local_get 531 | load 5 532 | - 533 | app_local_put 534 | int 1 535 | b l48 536 | l42: 537 | txn NumAppArgs 538 | int 3 539 | == 540 | bnz l44 541 | int 1 542 | b l45 543 | l44: 544 | int 0 545 | byte "LT" 546 | txna ApplicationArgs 0 547 | btoi 548 | app_local_put 549 | int 0 550 | byte "T1" 551 | txna ApplicationArgs 1 552 | btoi 553 | app_local_put 554 | int 0 555 | byte "T2" 556 | txna ApplicationArgs 2 557 | btoi 558 | app_local_put 559 | int 1 560 | l45: 561 | b l48 562 | int 1 563 | b l48 564 | int 1 565 | l48: -------------------------------------------------------------------------------- /build/manager_clear.teal: -------------------------------------------------------------------------------- 1 | #pragma version 2 2 | int 1 -------------------------------------------------------------------------------- /build/validator_approval.teal: -------------------------------------------------------------------------------- 1 | #pragma version 2 2 | txn ApplicationID 3 | int 0 4 | == 5 | bnz l30 6 | txn OnCompletion 7 | int CloseOut 8 | == 9 | bnz l12 10 | txn OnCompletion 11 | int OptIn 12 | == 13 | bnz l12 14 | txna ApplicationArgs 0 15 | byte "s1" 16 | == 17 | bnz l25 18 | txna ApplicationArgs 0 19 | byte "s2" 20 | == 21 | bnz l22 22 | txna ApplicationArgs 0 23 | byte "a" 24 | == 25 | bnz l19 26 | txna ApplicationArgs 0 27 | byte "w" 28 | == 29 | bnz l16 30 | txna ApplicationArgs 0 31 | byte "r" 32 | == 33 | bnz l13 34 | txna ApplicationArgs 0 35 | byte "p" 36 | == 37 | bnz l10 38 | err 39 | l10: 40 | int 1 41 | int 14973861 42 | byte "T1" 43 | app_local_get_ex 44 | store 0 45 | store 1 46 | int 1 47 | int 14973861 48 | byte "T2" 49 | app_local_get_ex 50 | store 2 51 | store 3 52 | global GroupSize 53 | int 4 54 | == 55 | txn GroupIndex 56 | int 0 57 | == 58 | && 59 | txn OnCompletion 60 | int NoOp 61 | == 62 | && 63 | txn NumAccounts 64 | int 1 65 | == 66 | && 67 | txn NumAppArgs 68 | int 1 69 | == 70 | && 71 | txn Sender 72 | byte "C" 73 | app_global_get 74 | == 75 | && 76 | gtxn 1 TypeEnum 77 | int appl 78 | == 79 | && 80 | gtxn 1 OnCompletion 81 | int NoOp 82 | == 83 | && 84 | gtxn 1 NumAccounts 85 | int 1 86 | == 87 | && 88 | gtxn 1 NumAppArgs 89 | int 1 90 | == 91 | && 92 | gtxna 1 Accounts 1 93 | txna Accounts 1 94 | == 95 | && 96 | gtxna 1 ApplicationArgs 0 97 | txna ApplicationArgs 0 98 | == 99 | && 100 | gtxn 1 Sender 101 | byte "C" 102 | app_global_get 103 | == 104 | && 105 | gtxn 2 TypeEnum 106 | int axfer 107 | == 108 | && 109 | gtxn 2 XferAsset 110 | load 1 111 | == 112 | && 113 | gtxn 2 Sender 114 | txna Accounts 1 115 | == 116 | && 117 | gtxn 2 AssetSender 118 | global ZeroAddress 119 | == 120 | && 121 | gtxn 3 TypeEnum 122 | int axfer 123 | == 124 | && 125 | gtxn 3 XferAsset 126 | load 3 127 | == 128 | && 129 | gtxn 3 Sender 130 | txna Accounts 1 131 | == 132 | && 133 | gtxn 3 AssetSender 134 | global ZeroAddress 135 | == 136 | && 137 | bnz l12 138 | err 139 | l12: 140 | int 1 141 | b l31 142 | l13: 143 | global GroupSize 144 | int 3 145 | == 146 | txn GroupIndex 147 | int 0 148 | == 149 | && 150 | txn OnCompletion 151 | int NoOp 152 | == 153 | && 154 | txn NumAccounts 155 | int 1 156 | == 157 | && 158 | txn NumAppArgs 159 | int 1 160 | == 161 | && 162 | gtxn 1 TypeEnum 163 | int appl 164 | == 165 | && 166 | gtxn 1 OnCompletion 167 | int NoOp 168 | == 169 | && 170 | gtxn 1 NumAccounts 171 | int 1 172 | == 173 | && 174 | gtxn 1 NumAppArgs 175 | int 1 176 | == 177 | && 178 | gtxna 1 Accounts 1 179 | txna Accounts 1 180 | == 181 | && 182 | gtxna 1 ApplicationArgs 0 183 | txna ApplicationArgs 0 184 | == 185 | && 186 | gtxn 2 TypeEnum 187 | int axfer 188 | == 189 | && 190 | gtxn 2 Sender 191 | txna Accounts 1 192 | == 193 | && 194 | gtxn 2 AssetSender 195 | global ZeroAddress 196 | == 197 | && 198 | bnz l15 199 | err 200 | l15: 201 | int 1 202 | b l31 203 | l16: 204 | int 1 205 | int 14973861 206 | byte "LT" 207 | app_local_get_ex 208 | store 4 209 | store 5 210 | global GroupSize 211 | int 3 212 | == 213 | txn GroupIndex 214 | int 0 215 | == 216 | && 217 | txn OnCompletion 218 | int NoOp 219 | == 220 | && 221 | txn NumAccounts 222 | int 1 223 | == 224 | && 225 | txn NumAppArgs 226 | int 3 227 | == 228 | && 229 | gtxn 1 TypeEnum 230 | int appl 231 | == 232 | && 233 | gtxn 1 OnCompletion 234 | int NoOp 235 | == 236 | && 237 | gtxn 1 NumAccounts 238 | int 1 239 | == 240 | && 241 | gtxn 1 NumAppArgs 242 | int 3 243 | == 244 | && 245 | gtxna 1 Accounts 1 246 | txna Accounts 1 247 | == 248 | && 249 | gtxna 1 ApplicationArgs 0 250 | txna ApplicationArgs 0 251 | == 252 | && 253 | gtxna 1 ApplicationArgs 1 254 | txna ApplicationArgs 1 255 | == 256 | && 257 | gtxna 1 ApplicationArgs 2 258 | txna ApplicationArgs 2 259 | == 260 | && 261 | gtxn 2 TypeEnum 262 | int axfer 263 | == 264 | && 265 | gtxn 2 XferAsset 266 | load 5 267 | == 268 | && 269 | gtxn 2 AssetSender 270 | global ZeroAddress 271 | == 272 | && 273 | gtxn 2 AssetReceiver 274 | txna Accounts 1 275 | == 276 | && 277 | gtxn 2 CloseRemainderTo 278 | global ZeroAddress 279 | == 280 | && 281 | gtxn 2 AssetCloseTo 282 | global ZeroAddress 283 | == 284 | && 285 | bnz l18 286 | err 287 | l18: 288 | int 1 289 | b l31 290 | l19: 291 | int 1 292 | int 14973861 293 | byte "T1" 294 | app_local_get_ex 295 | store 0 296 | store 1 297 | int 1 298 | int 14973861 299 | byte "T2" 300 | app_local_get_ex 301 | store 2 302 | store 3 303 | global GroupSize 304 | int 4 305 | == 306 | txn GroupIndex 307 | int 0 308 | == 309 | && 310 | txn OnCompletion 311 | int NoOp 312 | == 313 | && 314 | txn NumAccounts 315 | int 1 316 | == 317 | && 318 | txn NumAppArgs 319 | int 2 320 | == 321 | && 322 | gtxn 1 TypeEnum 323 | int appl 324 | == 325 | && 326 | gtxn 1 OnCompletion 327 | int NoOp 328 | == 329 | && 330 | gtxn 1 NumAccounts 331 | int 1 332 | == 333 | && 334 | gtxn 1 NumAppArgs 335 | int 2 336 | == 337 | && 338 | gtxna 1 Accounts 1 339 | txna Accounts 1 340 | == 341 | && 342 | gtxna 1 ApplicationArgs 0 343 | txna ApplicationArgs 0 344 | == 345 | && 346 | gtxna 1 ApplicationArgs 1 347 | txna ApplicationArgs 1 348 | == 349 | && 350 | gtxn 2 TypeEnum 351 | int axfer 352 | == 353 | && 354 | gtxn 2 XferAsset 355 | load 1 356 | == 357 | && 358 | gtxn 2 AssetSender 359 | global ZeroAddress 360 | == 361 | && 362 | gtxn 2 AssetReceiver 363 | txna Accounts 1 364 | == 365 | && 366 | gtxn 2 CloseRemainderTo 367 | global ZeroAddress 368 | == 369 | && 370 | gtxn 2 AssetCloseTo 371 | global ZeroAddress 372 | == 373 | && 374 | gtxn 3 TypeEnum 375 | int axfer 376 | == 377 | && 378 | gtxn 3 XferAsset 379 | load 3 380 | == 381 | && 382 | gtxn 3 AssetSender 383 | global ZeroAddress 384 | == 385 | && 386 | gtxn 3 AssetReceiver 387 | txna Accounts 1 388 | == 389 | && 390 | gtxn 3 CloseRemainderTo 391 | global ZeroAddress 392 | == 393 | && 394 | gtxn 3 AssetCloseTo 395 | global ZeroAddress 396 | == 397 | && 398 | bnz l21 399 | err 400 | l21: 401 | int 1 402 | b l31 403 | l22: 404 | int 1 405 | int 14973861 406 | byte "T2" 407 | app_local_get_ex 408 | store 2 409 | store 3 410 | global GroupSize 411 | int 3 412 | == 413 | txn GroupIndex 414 | int 0 415 | == 416 | && 417 | txn OnCompletion 418 | int NoOp 419 | == 420 | && 421 | txn NumAccounts 422 | int 1 423 | == 424 | && 425 | txn NumAppArgs 426 | int 2 427 | == 428 | && 429 | gtxn 1 TypeEnum 430 | int appl 431 | == 432 | && 433 | gtxn 1 OnCompletion 434 | int NoOp 435 | == 436 | && 437 | gtxn 1 NumAccounts 438 | int 1 439 | == 440 | && 441 | gtxn 1 NumAppArgs 442 | int 2 443 | == 444 | && 445 | gtxna 1 Accounts 1 446 | txna Accounts 1 447 | == 448 | && 449 | gtxna 1 ApplicationArgs 0 450 | txna ApplicationArgs 0 451 | == 452 | && 453 | gtxna 1 ApplicationArgs 1 454 | txna ApplicationArgs 1 455 | == 456 | && 457 | gtxn 2 TypeEnum 458 | int axfer 459 | == 460 | && 461 | gtxn 2 XferAsset 462 | load 3 463 | == 464 | && 465 | gtxn 2 AssetSender 466 | global ZeroAddress 467 | == 468 | && 469 | gtxn 2 AssetReceiver 470 | txna Accounts 1 471 | == 472 | && 473 | gtxn 2 CloseRemainderTo 474 | global ZeroAddress 475 | == 476 | && 477 | gtxn 2 AssetCloseTo 478 | global ZeroAddress 479 | == 480 | && 481 | bnz l24 482 | err 483 | l24: 484 | int 1 485 | b l31 486 | l25: 487 | int 1 488 | int 14973861 489 | byte "T1" 490 | app_local_get_ex 491 | store 0 492 | store 1 493 | global GroupSize 494 | int 3 495 | == 496 | txn GroupIndex 497 | int 0 498 | == 499 | && 500 | txn OnCompletion 501 | int NoOp 502 | == 503 | && 504 | txn NumAccounts 505 | int 1 506 | == 507 | && 508 | txn NumAppArgs 509 | int 2 510 | == 511 | && 512 | gtxn 1 TypeEnum 513 | int appl 514 | == 515 | && 516 | gtxn 1 OnCompletion 517 | int NoOp 518 | == 519 | && 520 | gtxn 1 NumAccounts 521 | int 1 522 | == 523 | && 524 | gtxn 1 NumAppArgs 525 | int 2 526 | == 527 | && 528 | gtxna 1 Accounts 1 529 | txna Accounts 1 530 | == 531 | && 532 | gtxna 1 ApplicationArgs 0 533 | txna ApplicationArgs 0 534 | == 535 | && 536 | gtxna 1 ApplicationArgs 1 537 | txna ApplicationArgs 1 538 | == 539 | && 540 | gtxn 2 TypeEnum 541 | int axfer 542 | == 543 | && 544 | gtxn 2 XferAsset 545 | load 1 546 | == 547 | && 548 | gtxn 2 AssetSender 549 | global ZeroAddress 550 | == 551 | && 552 | gtxn 2 AssetReceiver 553 | txna Accounts 1 554 | == 555 | && 556 | gtxn 2 CloseRemainderTo 557 | global ZeroAddress 558 | == 559 | && 560 | gtxn 2 AssetCloseTo 561 | global ZeroAddress 562 | == 563 | && 564 | bnz l27 565 | err 566 | l27: 567 | int 1 568 | b l31 569 | int 1 570 | b l31 571 | int 1 572 | b l31 573 | l30: 574 | byte "C" 575 | txn Sender 576 | app_global_put 577 | int 1 578 | l31: -------------------------------------------------------------------------------- /build/validator_clear.teal: -------------------------------------------------------------------------------- 1 | #pragma version 2 2 | int 1 -------------------------------------------------------------------------------- /config.sh: -------------------------------------------------------------------------------- 1 | export ALGOD_ENDPOINT="https://testnet-algorand.api.purestake.io/ps2" 2 | export ALGOD_TOKEN="LGG679KlpG3kSNMWRpbr48XhlCUknSEC7gO0d1I5" 3 | export INDEXER_ENDPOINT="https://testnet-algorand.api.purestake.io/idx2" 4 | export INDEXER_TOKEN="$ALGOD_TOKEN" 5 | 6 | export DEVELOPER_ACCOUNT_PRIVATE_KEY="plastic knee brush fall estate object blast harbor ethics acoustic wheat road parade allow tumble rally spell goddess someone coil disorder trumpet peanut above awkward" 7 | export TEST_ACCOUNT_PRIVATE_KEY="ladder main horn village pave disease effort surround stool purpose clown cereal sick stamp estate hotel foot argue blood mobile pattern maximum human able grunt" 8 | 9 | export ESCROW_ADDRESS="R3CB4EH5HBPWU5DT7QUFSXFNP44QFAUZYCVKCQIPIPTGPTCMHQC2JIOAVA" 10 | export ESCROW_LOGICSIG="AiALAQMEBqb3kQel95EHAuLBoAan95EHqPeRB6r3kQcyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=" 11 | 12 | export MANAGER_INDEX="14973861" 13 | export VALIDATOR_INDEX="14973862" 14 | export TOKEN1_INDEX="14973863" 15 | export TOKEN2_INDEX="14973864" 16 | export LIQUIDITY_TOKEN_INDEX="14973866" 17 | -------------------------------------------------------------------------------- /contracts/escrow.py: -------------------------------------------------------------------------------- 1 | from pyteal import * 2 | 3 | manager_application_id = Int(14973861) # TODO: Update 4 | validator_application_id = Int(14973862) # TODO: Update 5 | token1_asset_id = Int(14973863) # TODO: Update 6 | token2_asset_id = Int(14973864) # TODO: Update 7 | liquidity_token_asset_id = Int(14973866) # TODO: Update 8 | optin_last_valid = Int(13115618) # TODO: Update 9 | 10 | def logicsig(): 11 | """ 12 | This smart contract implements the Escrow part of the AlgoSwap DEX. 13 | It is a logicsig smart contract for a specific liquidity pair (Token 1/Token 2) 14 | where Token 1 and Token 2 are distinct Algorand Standard Assets (ASAs). 15 | All withdrawals from this smart contract account require approval from the 16 | Validator and Manager contracts first within the same atomic transaction group. 17 | """ 18 | program = Cond( 19 | [ 20 | # If there is a single transaction within the group 21 | Global.group_size() == Int(1), 22 | # Then either this is an opt-in to a contract, or to an asset 23 | Or( 24 | And( 25 | # This is a contract opt-in transaction 26 | Txn.on_completion() == OnComplete.OptIn, 27 | # Transaction's last valid round is lte specified last valid round 28 | Txn.last_valid() <= optin_last_valid, 29 | Or( 30 | # Is an opt in to the validator contract 31 | Txn.application_id() == validator_application_id, 32 | # Is an opt in to the manager contract 33 | Txn.application_id() == manager_application_id 34 | ) 35 | ), 36 | And( 37 | # This is an asset opt-in 38 | Txn.type_enum() == TxnType.AssetTransfer, 39 | # Sender and asset receiver are both Escrow 40 | Txn.sender() == Txn.asset_receiver(), 41 | # Transaction's last valid round is lte specified last valid round 42 | Txn.last_valid() <= optin_last_valid, 43 | # Is an opt-in to one of the expected assets 44 | Or( 45 | # Is an opt in to Token 1 Asset 46 | Txn.xfer_asset() == token1_asset_id, 47 | # Is an opt in to Token 2 Asset 48 | Txn.xfer_asset() == token2_asset_id, 49 | # Is an opt in to Liquidity Pair Token Asset 50 | Txn.xfer_asset() == liquidity_token_asset_id 51 | ) 52 | ) 53 | ) 54 | ], 55 | [ 56 | # If there are three transactions within the group 57 | Global.group_size() == Int(3), 58 | # Then this is a refund transaction 59 | And( 60 | # first one is an ApplicationCall 61 | Gtxn[0].type_enum() == TxnType.ApplicationCall, 62 | # the ApplicationCall must be approved by the validator application 63 | Gtxn[0].application_id() == validator_application_id, 64 | 65 | # second one is an ApplicationCall 66 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 67 | # Must be approved by the manager application 68 | Gtxn[1].application_id() == manager_application_id, 69 | 70 | # this transaction is the third one 71 | Txn.group_index() == Int(2), 72 | # this transaction is an AssetTransfer 73 | Txn.type_enum() == TxnType.AssetTransfer, 74 | # this transaction is not a close transaction 75 | Txn.close_remainder_to() == Global.zero_address(), 76 | # this transaction is not an asset close transaction 77 | Txn.asset_close_to() == Global.zero_address() 78 | ) 79 | ], 80 | [ 81 | # If there are four transactions within the group 82 | Global.group_size() == Int(4), 83 | # Then this is a withdraw protocol fees transaction 84 | And( 85 | # first one is an ApplicationCall 86 | # first one is an ApplicationCall 87 | Gtxn[0].type_enum() == TxnType.ApplicationCall, 88 | # the ApplicationCall must be approved by the validator application 89 | Gtxn[0].application_id() == validator_application_id, 90 | 91 | # second one is an ApplicationCall 92 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 93 | # Must be approved by the manager application 94 | Gtxn[1].application_id() == manager_application_id, 95 | 96 | # this transaction is the third or fourth one 97 | Or( 98 | Txn.group_index() == Int(2), 99 | Txn.group_index() == Int(3), 100 | ), 101 | # this transaction is an AssetTransfer 102 | Txn.type_enum() == TxnType.AssetTransfer, 103 | # this transaction is not a close transaction 104 | Txn.close_remainder_to() == Global.zero_address(), 105 | # this transaction is not an asset close transaction 106 | Txn.asset_close_to() == Global.zero_address(), 107 | ) 108 | ] 109 | ) 110 | return program 111 | 112 | 113 | if __name__ == "__main__": 114 | print(compileTeal(logicsig, Mode.Signature)) 115 | -------------------------------------------------------------------------------- /contracts/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pyteal import * 4 | 5 | def test(): 6 | on_opt_in = Seq([ 7 | App.localPut(Int(0), Bytes("T1"), Int(0)), 8 | Int(1) 9 | ]) 10 | 11 | t1_val = App.localGet(Int(1), Bytes("T1")) 12 | 13 | on_function = Seq( 14 | [Assert( 15 | And( 16 | Txn.accounts.length() == Int(1), 17 | t1_val == Btoi(Txn.application_args[0]) 18 | ) 19 | ), 20 | Int(1) 21 | ]) 22 | 23 | program = Cond( 24 | [Txn.on_completion() == OnComplete.OptIn,on_opt_in], 25 | [Txn.on_completion() == OnComplete.NoOp, on_function] 26 | ) 27 | return program 28 | 29 | 30 | if __name__ == "__main__": 31 | print(compileTeal(test(), Mode.Application)) 32 | -------------------------------------------------------------------------------- /contracts/validator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pyteal import * 4 | 5 | # Manager App ID 6 | MANAGER_INDEX = Int(14973861) # TODO: Update 7 | 8 | # Keys 9 | KEY_CREATOR = Bytes("C") 10 | KEY_TOKEN1 = Bytes("T1") 11 | KEY_TOKEN2 = Bytes("T2") 12 | KEY_LIQUIDITY_TOKEN = Bytes("LT") 13 | 14 | # Transaction Types 15 | TRANSACTION_TYPE_SWAP_DEPOSIT_TOKEN1_TO_TOKEN2 = Bytes("s1") 16 | TRANSACTION_TYPE_SWAP_DEPOSIT_TOKEN2_TO_TOKEN1 = Bytes("s2") 17 | TRANSACTION_TYPE_ADD_LIQUIDITY_DEPOSIT = Bytes("a") 18 | TRANSACTION_TYPE_WITHDRAW_LIQUIDITY = Bytes("w") 19 | TRANSACTION_TYPE_REFUND = Bytes("r") 20 | TRANSACTION_TYPE_WITHDRAW_PROTOCOL_FEES = Bytes("p") 21 | 22 | def approval_program(): 23 | """ 24 | This smart contract implements the Validator part of the AlgoSwap DEX. 25 | It asserts the existence of all required transaction fields in every 26 | transaction part of every possible atomic transaction group that AlgoSwap 27 | supports (Swap Token 1 for Token 2, Swap Token 2 for Token 1, Add Liquidity, 28 | Withdraw Liquidity, Withdraw Protocol Fees, and Refund). 29 | 30 | Any atomic transaction group MUST have a transaction to the validator 31 | smart contract as the first transaction of the group to proceed. 32 | 33 | Commands: 34 | s1 Swap Token 1 for Token 2 in a liquidity pair 35 | s2 Swap Token 2 for Token 1 in a liquidity pair 36 | a Add liquidity to a liquidity pool 37 | w Withdraw liquidity from a liquidity pool 38 | r Get a refund of unused tokens 39 | p Withdraw protocol fees (Developer only) 40 | """ 41 | 42 | key_token1 = App.localGetEx(Int(1), MANAGER_INDEX, KEY_TOKEN1) 43 | key_token2 = App.localGetEx(Int(1), MANAGER_INDEX, KEY_TOKEN2) 44 | key_liquidity_token = App.localGetEx(Int(1), MANAGER_INDEX, KEY_LIQUIDITY_TOKEN) 45 | 46 | # On application create, put the creator key in global storage 47 | on_create = Seq([ 48 | App.globalPut(KEY_CREATOR, Txn.sender()), 49 | Int(1) 50 | ]) 51 | 52 | # Closeout on validator does nothing 53 | on_closeout = Int(1) 54 | 55 | # Opt in on validator does nothing 56 | on_opt_in = Int(1) 57 | 58 | on_swap_deposit = Seq([ 59 | key_token1, 60 | Assert( 61 | And( 62 | # Group has 3 transactions 63 | Global.group_size() == Int(3), 64 | # This ApplicationCall is the 1st transaction 65 | Txn.group_index() == Int(0), 66 | # No additional actions are needed from this transaction 67 | Txn.on_completion() == OnComplete.NoOp, 68 | # Has one additional account attached 69 | Txn.accounts.length() == Int(1), 70 | # Has two application arguments 71 | Txn.application_args.length() == Int(2), 72 | 73 | # Second txn to manager 74 | 75 | # Is of type ApplicationCall 76 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 77 | # No additional actions needed 78 | Gtxn[1].on_completion() == OnComplete.NoOp, 79 | # Has one additional account attached 80 | Gtxn[1].accounts.length() == Int(1), 81 | # Has two application arguments 82 | Gtxn[1].application_args.length() == Int(2), 83 | # Additional account is same in both calls 84 | Txn.accounts[1] == Gtxn[1].accounts[1], 85 | # Application argument is same in both calls 86 | Txn.application_args[0] == Gtxn[1].application_args[0], 87 | Txn.application_args[1] == Gtxn[1].application_args[1], 88 | 89 | # Third txn to escrow 90 | 91 | # Is of type AssetTransfer 92 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 93 | # Transfer asset is TOKEN1 94 | Gtxn[2].xfer_asset() == key_token1.value(), 95 | # Asset sender is zero address 96 | Gtxn[2].asset_sender() == Global.zero_address(), 97 | # Asset receiver is attached account 98 | Gtxn[2].asset_receiver() == Txn.accounts[1], 99 | # Is not a close transaction 100 | Gtxn[2].close_remainder_to() == Global.zero_address(), 101 | # Is not a close asset transaction 102 | Gtxn[2].asset_close_to() == Global.zero_address(), 103 | ) 104 | ), 105 | Int(1) 106 | ]) 107 | 108 | on_swap_deposit_2 = Seq([ 109 | key_token2, 110 | Assert( 111 | And( 112 | # Group has 3 transactions 113 | Global.group_size() == Int(3), 114 | # This ApplicationCall is the first transaction 115 | Txn.group_index() == Int(0), 116 | # No additional actions are needed from this transaction 117 | Txn.on_completion() == OnComplete.NoOp, 118 | # Has one additional account attached 119 | Txn.accounts.length() == Int(1), 120 | # Has two application arguments attached 121 | Txn.application_args.length() == Int(2), 122 | 123 | # Second txn to Manager 124 | 125 | # Is of type ApplicationCall 126 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 127 | # No additional actions needed 128 | Gtxn[1].on_completion() == OnComplete.NoOp, 129 | # Has one additional account attached 130 | Gtxn[1].accounts.length() == Int(1), 131 | # Has two application arguments attached 132 | Gtxn[1].application_args.length() == Int(2), 133 | # Additional account is same as first txn 134 | Txn.accounts[1] == Gtxn[1].accounts[1], 135 | # Application arguments are same as first txn 136 | Txn.application_args[0] == Gtxn[1].application_args[0], 137 | Txn.application_args[1] == Gtxn[1].application_args[1], 138 | 139 | # Third txn to escrow 140 | 141 | # Is of type AssetTransfer 142 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 143 | # Transfer asset is Token 2 144 | Gtxn[2].xfer_asset() == key_token2.value(), 145 | # Sender is zero address 146 | Gtxn[2].asset_sender() == Global.zero_address(), 147 | # Asset receiver is attached account 148 | Gtxn[2].asset_receiver() == Txn.accounts[1], 149 | # Is not a close transaction 150 | Gtxn[2].close_remainder_to() == Global.zero_address(), 151 | # Is not a close asset transaction 152 | Gtxn[2].asset_close_to() == Global.zero_address(), 153 | ) 154 | ), 155 | Int(1) 156 | ]) 157 | 158 | on_add_liquidity_deposit = Seq([ 159 | key_token1, 160 | key_token2, 161 | Assert( 162 | And( 163 | # Group has 4 transactions 164 | Global.group_size() == Int(4), 165 | # This ApplicationCall is the first transaction 166 | Txn.group_index() == Int(0), 167 | # No additional actions needed from this transaction 168 | Txn.on_completion() == OnComplete.NoOp, 169 | # Has one additional account attached 170 | Txn.accounts.length() == Int(1), 171 | # Has two application arguments attached 172 | Txn.application_args.length() == Int(2), 173 | 174 | # NOTE: No way to check length of foreign assets in PyTeal 175 | 176 | # Second txn to Manager 177 | 178 | # is of type ApplicationCall 179 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 180 | # No additional actions needed 181 | Gtxn[1].on_completion() == OnComplete.NoOp, 182 | # Has one additional account attached 183 | Gtxn[1].accounts.length() == Int(1), 184 | # Has two application arguments attached 185 | Gtxn[1].application_args.length() == Int(2), 186 | # Additional accounts are same as first txn 187 | Txn.accounts[1] == Gtxn[1].accounts[1], 188 | # Application arguments are same as first txn 189 | Txn.application_args[0] == Gtxn[1].application_args[0], 190 | Txn.application_args[1] == Gtxn[1].application_args[1], 191 | 192 | # Third txn to Escrow 193 | 194 | # Is of type AssetTransfer 195 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 196 | # Transfer asset is Token 1 197 | Gtxn[2].xfer_asset() == key_token1.value(), 198 | # Asset sender is zero address 199 | Gtxn[2].asset_sender() == Global.zero_address(), 200 | # Asset receiver is the escrow account 201 | Gtxn[2].asset_receiver() == Txn.accounts[1], 202 | # Is not a close transaction 203 | Gtxn[2].close_remainder_to() == Global.zero_address(), 204 | # Is not a close asset transaction 205 | Gtxn[2].asset_close_to() == Global.zero_address(), 206 | 207 | # Fourth txn to Escrow 208 | 209 | # Is of type AssetTransfer 210 | Gtxn[3].type_enum() == TxnType.AssetTransfer, 211 | # Transfer asset is Token 2 212 | Gtxn[3].xfer_asset() == key_token2.value(), 213 | # Asset sender is zero address 214 | Gtxn[3].asset_sender() == Global.zero_address(), 215 | # Asset receiver is the escrow account 216 | Gtxn[3].asset_receiver() == Txn.accounts[1], 217 | # Is not a close transaction 218 | Gtxn[3].close_remainder_to() == Global.zero_address(), 219 | # Is not a close asset transaction 220 | Gtxn[3].asset_close_to() == Global.zero_address(), 221 | ) 222 | ), 223 | Int(1) 224 | ]) 225 | 226 | on_withdraw_liquidity = Seq([ 227 | key_liquidity_token, 228 | Assert( 229 | And( 230 | # Group has 3 transactions 231 | Global.group_size() == Int(3), 232 | # This ApplicationCall is the first transaction 233 | Txn.group_index() == Int(0), 234 | # No additional actions are needed from this transaction 235 | Txn.on_completion() == OnComplete.NoOp, 236 | # Has one additional account attached 237 | Txn.accounts.length() == Int(1), 238 | # Has three application arguments attached 239 | Txn.application_args.length() == Int(3), 240 | 241 | # NOTE: No way to check length of foreign assets in PyTeal 242 | 243 | # Second txn to Manager 244 | 245 | # is of type ApplicationCall 246 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 247 | # No additional actions needed 248 | Gtxn[1].on_completion() == OnComplete.NoOp, 249 | # Has two additional accounts attached 250 | Gtxn[1].accounts.length() == Int(1), 251 | # Has three application arguments attached 252 | Gtxn[1].application_args.length() == Int(3), 253 | # Additional accounts are same as first txn 254 | Txn.accounts[1] == Gtxn[1].accounts[1], 255 | # Application arguments are same as first txn 256 | Txn.application_args[0] == Gtxn[1].application_args[0], 257 | Txn.application_args[1] == Gtxn[1].application_args[1], 258 | Txn.application_args[2] == Gtxn[1].application_args[2], 259 | 260 | # Third txn to Escrow 261 | 262 | # is of type AssetTransfer 263 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 264 | # Transfer asset is liquidity token 265 | Gtxn[2].xfer_asset() == key_liquidity_token.value(), 266 | # Asset sender is zero address 267 | Gtxn[2].asset_sender() == Global.zero_address(), 268 | # Asset receiver is the escrow account 269 | Gtxn[2].asset_receiver() == Txn.accounts[1], 270 | # Is not a close transaction 271 | Gtxn[2].close_remainder_to() == Global.zero_address(), 272 | # Is not a close asset transaction 273 | Gtxn[2].asset_close_to() == Global.zero_address(), 274 | ) 275 | ), 276 | Int(1), 277 | ]) 278 | 279 | on_withdraw_protocol_fees = Seq([ 280 | key_token1, 281 | key_token2, 282 | Assert( 283 | And( 284 | # Group has 4 transactions 285 | Global.group_size() == Int(4), 286 | # This ApplicationCall is the first transaction 287 | Txn.group_index() == Int(0), 288 | # No additional actions needed from this transaction 289 | Txn.on_completion() == OnComplete.NoOp, 290 | # Has one additional account attached 291 | Txn.accounts.length() == Int(1), 292 | # Has one application argument attached 293 | Txn.application_args.length() == Int(1), 294 | # Sender is developer 295 | Txn.sender() == App.globalGet(KEY_CREATOR), 296 | 297 | # Second txn to Manager 298 | 299 | # is of type ApplicationCall 300 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 301 | # No additional actions needed 302 | Gtxn[1].on_completion() == OnComplete.NoOp, 303 | # Has one additional account attached 304 | Gtxn[1].accounts.length() == Int(1), 305 | # Has one application argument attached 306 | Gtxn[1].application_args.length() == Int(1), 307 | # Additional account is same as first txn 308 | Txn.accounts[1] == Gtxn[1].accounts[1], 309 | # Application argument is same as first txn 310 | Txn.application_args[0] == Gtxn[1].application_args[0], 311 | # Sender is developer 312 | Gtxn[1].sender() == App.globalGet(KEY_CREATOR), 313 | 314 | # Third txn from Escrow to Developer 315 | 316 | # is of type AssetTransfer 317 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 318 | # Transfer asset is Token 1 319 | Gtxn[2].xfer_asset() == key_token1.value(), 320 | # sender is escrow 321 | Gtxn[2].sender() == Txn.accounts[1], 322 | # is not a clawback transaction 323 | Gtxn[2].asset_sender() == Global.zero_address(), 324 | 325 | # Fourth txn from Escrow to Developer 326 | 327 | # is of type AssetTransfer 328 | Gtxn[3].type_enum() == TxnType.AssetTransfer, 329 | # Transfer asset is Token 2 330 | Gtxn[3].xfer_asset() == key_token2.value(), 331 | # sender is escrow 332 | Gtxn[3].sender() == Txn.accounts[1], 333 | # is not a clawback transaction 334 | Gtxn[3].asset_sender() == Global.zero_address(), 335 | ) 336 | ), 337 | Int(1) 338 | ]) 339 | 340 | on_refund = Seq([ 341 | Assert( 342 | And( 343 | # Group has 3 transactions 344 | Global.group_size() == Int(3), 345 | # This ApplicationCall is the first transaction 346 | Txn.group_index() == Int(0), 347 | # No additional actions needed from this transaction 348 | Txn.on_completion() == OnComplete.NoOp, 349 | # Has one additional account attached 350 | Txn.accounts.length() == Int(1), 351 | # Has one application argument attached 352 | Txn.application_args.length() == Int(1), 353 | 354 | # Second txn to Manager 355 | 356 | # is of type ApplicationCall 357 | Gtxn[1].type_enum() == TxnType.ApplicationCall, 358 | # No additional actions needed 359 | Gtxn[1].on_completion() == OnComplete.NoOp, 360 | # Has one additional account attached 361 | Gtxn[1].accounts.length() == Int(1), 362 | # Has one application argument attached 363 | Gtxn[1].application_args.length() == Int(1), 364 | # Additional account is same as first txn 365 | Txn.accounts[1] == Gtxn[1].accounts[1], 366 | # Application argument is same as first txn 367 | Txn.application_args[0] == Gtxn[1].application_args[0], 368 | 369 | # Third txn from Escrow 370 | 371 | # is of type AssetTransfer 372 | Gtxn[2].type_enum() == TxnType.AssetTransfer, 373 | # sender is escrow 374 | Gtxn[2].sender() == Txn.accounts[1], 375 | # is not a clawback transaction 376 | Gtxn[2].asset_sender() == Global.zero_address(), 377 | ) 378 | ), 379 | Int(1) 380 | ]) 381 | 382 | program = Cond( 383 | [Txn.application_id() == Int(0), 384 | on_create], 385 | [Txn.on_completion() == OnComplete.CloseOut, 386 | on_closeout], 387 | [Txn.on_completion() == OnComplete.OptIn, 388 | on_opt_in], 389 | [Txn.application_args[0] == TRANSACTION_TYPE_SWAP_DEPOSIT_TOKEN1_TO_TOKEN2, 390 | on_swap_deposit], 391 | [Txn.application_args[0] == TRANSACTION_TYPE_SWAP_DEPOSIT_TOKEN2_TO_TOKEN1, 392 | on_swap_deposit_2], 393 | [Txn.application_args[0] == TRANSACTION_TYPE_ADD_LIQUIDITY_DEPOSIT, 394 | on_add_liquidity_deposit], 395 | [Txn.application_args[0] == TRANSACTION_TYPE_WITHDRAW_LIQUIDITY, 396 | on_withdraw_liquidity], 397 | [Txn.application_args[0] == TRANSACTION_TYPE_REFUND, 398 | on_refund], 399 | [Txn.application_args[0] == TRANSACTION_TYPE_WITHDRAW_PROTOCOL_FEES, 400 | on_withdraw_protocol_fees], 401 | ) 402 | return program 403 | 404 | def clear_program(): 405 | return Int(1) 406 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [*.py] 12 | indent_size = 4 13 | continuation_indent_size = 8 14 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/.nvmrc: -------------------------------------------------------------------------------- 1 | 12.18.4 2 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "arrowParens": "avoid" 6 | } 7 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algoswap", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.4.0", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "@types/jest": "^24.0.0", 11 | "@types/node": "^12.0.0", 12 | "@types/react": "^16.9.0", 13 | "@types/react-dom": "^16.9.0", 14 | "@types/react-redux": "^7.1.9", 15 | "@types/react-router-dom": "^5.1.5", 16 | "algosdk": "^1.8.1", 17 | "react": "^16.13.1", 18 | "react-dom": "^16.13.1", 19 | "react-redux": "^7.2.1", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.3", 22 | "rodal": "^1.8.1", 23 | "sass": "^1.32.7", 24 | "typescript": "~3.7.2" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": "react-app" 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | }, 47 | "devDependencies": { 48 | "prettier": "2.1.2", 49 | "redux-devtools": "^3.7.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/public/algosigner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/algosigner.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | AlgoSwap 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "AlgoSwap", 3 | "name": "AlgoSwap", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/public/settings.png -------------------------------------------------------------------------------- /frontend/src/App.scss: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | background-image: white; 4 | background-position: 0px -30vh; 5 | min-height: 100vh; 6 | 7 | .App-container { 8 | padding-top: 100px; 9 | font-size: calc(10px + 2vmin); 10 | } 11 | 12 | .App-logo-modal { 13 | margin-right: 8px; 14 | height: 16px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {render} from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const {getByText} = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BrowserRouter as Router, Switch, Route, Redirect} from 'react-router-dom'; 3 | import NavigationBar from './components/NavigationBar'; 4 | import {Provider} from 'react-redux'; 5 | import {createStore} from 'redux'; 6 | 7 | import rootReducer from './redux/reducers'; 8 | import SwapPage from './pages/SwapPage'; 9 | import PoolPage from './pages/PoolPage'; 10 | import AddPage from './pages/AddPage'; 11 | 12 | import './App.scss'; 13 | import CreatePage from './pages/CreatePage'; 14 | 15 | const store = createStore(rootReducer); 16 | 17 | function App() { 18 | return ( 19 | 20 | 21 |
22 |
23 | WARNING: THIS CODE HAS NOT BEEN AUDITED AND SHOULD NOT BE USED ON THE ALGORAND MAINNET - 24 | USE AT YOUR OWN RISK! 25 |
26 | 27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | } /> 39 | } /> 40 | 41 |
42 |
43 |
44 |
45 | ); 46 | } 47 | 48 | export default App; 49 | -------------------------------------------------------------------------------- /frontend/src/assets/fonts/4/Overpass-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/src/assets/fonts/4/Overpass-Regular.woff -------------------------------------------------------------------------------- /frontend/src/assets/fonts/4/Overpass-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/src/assets/fonts/4/Overpass-Regular.woff2 -------------------------------------------------------------------------------- /frontend/src/assets/images/algosigner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hyplabs/AlgoSwap/4e3f9a977461cd34c246847a0f0d5dbbae5e5ee7/frontend/src/assets/images/algosigner.png -------------------------------------------------------------------------------- /frontend/src/components/AddLiquidity.scss: -------------------------------------------------------------------------------- 1 | .AddLiquidity { 2 | position: relative; 3 | margin: auto; 4 | width: 100%; 5 | max-width: 420px; 6 | border-radius: 30px; 7 | border: 2px solid lightgray; 8 | padding: 20px 1.5rem 20px 1.5rem; 9 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.16); 10 | 11 | .AddLiquidity-header { 12 | display: flex; 13 | align-items: flex-start; 14 | margin-bottom: 20px; 15 | font-size: 20px; 16 | font-weight: 600; 17 | } 18 | 19 | .AddLiquidity-plus { 20 | font-size: 16px; 21 | } 22 | 23 | .AddLiquidity-add { 24 | margin-top: 16px; 25 | background-color: #132d5a; 26 | color: #ffffff; 27 | padding: 8px 16px; 28 | border: 1px solid #132d5a; 29 | border-radius: 8px; 30 | cursor: pointer; 31 | } 32 | 33 | .AddLiquidity-bottom { 34 | margin-top: 25px; 35 | 36 | .AddLiquidity-button { 37 | background-color: #a30786; 38 | color: #ffffff; 39 | padding: 10px 15px; 40 | width: 100%; 41 | height: 100%; 42 | font-size: 18px; 43 | border: 1px solid #a30786; 44 | border-radius: 12px; 45 | text-transform: uppercase; 46 | font-weight: 600; 47 | cursor: pointer; 48 | 49 | &:hover { 50 | background-color: #940678; 51 | } 52 | } 53 | } 54 | 55 | .AddLiquidity-wallet-modal { 56 | height: 100%; 57 | display: flex; 58 | flex-direction: column; 59 | justify-content: flex-start; 60 | font-weight: 700; 61 | 62 | .AddLiquidity-wallet-modal-header { 63 | display: flex; 64 | padding: 10px; 65 | font-size: 18px; 66 | border-bottom: 1px solid lightgray; 67 | .AddLiquidity-wallet-modal-header-image { 68 | display: flex; 69 | align-items: center; 70 | margin-right: 5px; 71 | } 72 | } 73 | 74 | .AddLiquidity-wallet-modal-select { 75 | background-color: transparent; 76 | border: 2px solid lightgray; 77 | margin: 15px; 78 | border-radius: 12px; 79 | font-size: 18px; 80 | font-weight: inherit; 81 | 82 | .AddLiquidity-wallet-modal-item { 83 | display: flex; 84 | justify-content: space-between; 85 | align-items: center; 86 | padding: 15px; 87 | } 88 | 89 | &:hover { 90 | border: 2px solid #940678; 91 | } 92 | } 93 | } 94 | 95 | .Wallet-logo-modal { 96 | height: 45px; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /frontend/src/components/AddLiquidity.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {useSelector, useDispatch} from 'react-redux'; 3 | 4 | import {selectUserAccountAddress} from '../redux/reducers/user'; 5 | import {selectTokenList} from '../redux/reducers/tokens'; 6 | import {setAccountAddress, setTokenList, setFirstToken, setSecondToken} from '../redux/actions'; 7 | 8 | import TokenAmount from './TokenAmount/TokenAmount'; 9 | 10 | /* eslint-dsiable */ 11 | // @ts-ignore 12 | import Rodal from 'rodal'; 13 | import 'rodal/lib/rodal.css'; 14 | 15 | import './AddLiquidity.scss'; 16 | 17 | interface Props { 18 | firstToken: string; 19 | secondToken: string; 20 | updateTokens: (first: string, second: string) => void; 21 | } 22 | 23 | const AddLiquidity: React.FC = ({firstToken, secondToken, updateTokens}) => { 24 | // Local state 25 | const [firstAmount, setFirstAmount] = useState(''); 26 | const [secondAmount, setSecondAmount] = useState(''); 27 | 28 | const [firstTabSelected, setFirstTabSelected] = useState(false); 29 | const [secondTabSelected, setSecondTabSelected] = useState(false); 30 | const [openModal, setOpenModal] = useState(false); 31 | 32 | // Redux state 33 | const walletAddr = useSelector(selectUserAccountAddress); 34 | const tokenList = useSelector(selectTokenList); 35 | 36 | const dispatch = useDispatch(); 37 | 38 | const onFirstRender = () => { 39 | dispatch( 40 | setTokenList([ 41 | ['ETH', '0'], 42 | ['BTC', '1'], 43 | ['ALG', '2'], 44 | ['USD', '3'], 45 | ]) 46 | ); 47 | dispatch(setAccountAddress('')); 48 | }; 49 | 50 | useEffect(() => { 51 | onFirstRender(); 52 | }, []); 53 | 54 | const toggleModal = () => { 55 | setOpenModal(!openModal); 56 | }; 57 | 58 | function setActiveTab(type: string) { 59 | if (firstTabSelected === false && secondTabSelected === false) { 60 | if (type === 'First') { 61 | setFirstTabSelected(true); 62 | } 63 | if (type === 'Second') { 64 | setSecondTabSelected(true); 65 | } 66 | } else if (firstTabSelected === true) { 67 | if (type === 'Second') { 68 | setFirstTabSelected(false); 69 | setSecondTabSelected(true); 70 | } 71 | } else { 72 | if (type === 'First') { 73 | setFirstTabSelected(true); 74 | setSecondTabSelected(false); 75 | } 76 | } 77 | } 78 | 79 | const modalStyle = { 80 | position: 'relative', 81 | 'border-radius': '30px', 82 | top: '210px', 83 | }; 84 | 85 | return ( 86 |
87 |
Add Liquidity
88 |
89 | setFirstAmount(amount)} 93 | tokenList={tokenList} 94 | token={firstToken} 95 | updateToken={token => { 96 | dispatch(setFirstToken(token)); 97 | updateTokens(token, secondToken); 98 | }} 99 | active={firstTabSelected} 100 | onClick={() => setActiveTab('First')} 101 | /> 102 |

+

103 | setSecondAmount(amount)} 107 | tokenList={tokenList} 108 | token={secondToken} 109 | updateToken={token => { 110 | if (firstToken === '') { 111 | dispatch(setFirstToken(tokenList[0][0])); 112 | dispatch(setSecondToken(token)); 113 | updateTokens(tokenList[0][0], token); 114 | } else { 115 | dispatch(setSecondToken(token)); 116 | updateTokens(firstToken, token); 117 | } 118 | }} 119 | active={secondTabSelected} 120 | onClick={() => setActiveTab('Second')} 121 | /> 122 |
123 |
124 | {walletAddr ? ( 125 | 126 | ) : ( 127 | 130 | )} 131 |
132 | 140 |
141 |
142 |
143 | AlgoSwap 144 |
145 | Connect to a wallet 146 |
147 | 153 |
154 |
155 |
156 | ); 157 | }; 158 | 159 | export default AddLiquidity; 160 | -------------------------------------------------------------------------------- /frontend/src/components/CreatePair.scss: -------------------------------------------------------------------------------- 1 | .CreatePair { 2 | position: relative; 3 | margin: auto; 4 | width: 100%; 5 | max-width: 420px; 6 | border-radius: 30px; 7 | border: 2px solid lightgray; 8 | padding: 20px 1.5rem 20px 1.5rem; 9 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.16); 10 | 11 | .CreatePair-header { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | margin-bottom: 20px; 16 | font-size: 20px; 17 | font-weight: 600; 18 | } 19 | 20 | .CreatePair-plus { 21 | font-size: 16px; 22 | } 23 | 24 | .CreatePair-add { 25 | margin-top: 16px; 26 | background-color: #132d5a; 27 | color: #ffffff; 28 | padding: 8px 16px; 29 | border: 1px solid #132d5a; 30 | border-radius: 8px; 31 | cursor: pointer; 32 | } 33 | 34 | .CreatePair-bottom { 35 | margin-top: 25px; 36 | 37 | .CreatePair-button { 38 | background-color: #a30786; 39 | color: #ffffff; 40 | padding: 10px 15px; 41 | width: 100%; 42 | height: 100%; 43 | font-size: 18px; 44 | border: 1px solid #a30786; 45 | border-radius: 12px; 46 | text-transform: uppercase; 47 | font-weight: 600; 48 | cursor: pointer; 49 | 50 | &:hover { 51 | background-color: #940678; 52 | } 53 | } 54 | 55 | .CreatePair-button-disabled { 56 | background-color: lightgray !important; 57 | border: 1px solid lightgray !important; 58 | } 59 | } 60 | 61 | .Settings-button { 62 | background-color: transparent; 63 | border: transparent; 64 | outline: none; 65 | 66 | .Settings-logo { 67 | height: 25px; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /frontend/src/components/CreatePair.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {useSelector, useDispatch} from 'react-redux'; 3 | 4 | import {selectUserAccountAddress} from '../redux/reducers/user'; 5 | import {selectTokenList} from '../redux/reducers/tokens'; 6 | import {setAccountAddress, setTokenList, setFirstToken, setSecondToken} from '../redux/actions'; 7 | 8 | import TokenAmount from './TokenAmount/TokenAmount'; 9 | 10 | import './CreatePair.scss'; 11 | import SettingsModal from './common/SettingsModal'; 12 | import WalletModal from './common/WalletModal'; 13 | import CreatePairModal from './CreatePairModal'; 14 | 15 | interface Props { 16 | firstToken: string; 17 | secondToken: string; 18 | updateTokens: (first: string, second: string) => void; 19 | } 20 | 21 | const CreatePair: React.FC = ({firstToken, secondToken, updateTokens}) => { 22 | // Local state 23 | const [firstAmount, setFirstAmount] = useState(''); 24 | const [secondAmount, setSecondAmount] = useState(''); 25 | 26 | const [firstTabSelected, setFirstTabSelected] = useState(false); 27 | const [secondTabSelected, setSecondTabSelected] = useState(false); 28 | 29 | // Modals 30 | const [openWalletModal, setOpenWalletModal] = useState(false); 31 | const [openSettingsModal, setOpenSettingsModal] = useState(false); 32 | const [openSupplyModal, setOpenSupplyodal] = useState(false); 33 | 34 | // Redux state 35 | const walletAddr = useSelector(selectUserAccountAddress); 36 | const tokenList = useSelector(selectTokenList); 37 | 38 | const dispatch = useDispatch(); 39 | 40 | const onFirstRender = () => { 41 | dispatch( 42 | setTokenList([ 43 | ['ETH', '0'], 44 | ['BTC', '1'], 45 | ['ALG', '2'], 46 | ['USD', '3'], 47 | ]) 48 | ); 49 | dispatch(setAccountAddress('')); 50 | }; 51 | 52 | useEffect(() => { 53 | onFirstRender(); 54 | }, []); 55 | 56 | const toggleWalletModal = () => { 57 | setOpenWalletModal(!openWalletModal); 58 | }; 59 | 60 | const toggleSettingsModal = () => { 61 | setOpenSettingsModal(!openSettingsModal); 62 | }; 63 | 64 | const toggleSupplyModal = () => { 65 | setOpenSupplyodal(!openSupplyModal); 66 | }; 67 | 68 | const bothTokensNotSet = () => { 69 | if (firstToken === '' || secondToken === '') { 70 | return true; 71 | } 72 | /* 73 | TODO: 74 | If one of the input panels are given the value, the other 75 | one should be automatically calculated using the conversion rate. 76 | 77 | For now, the swap button is clickable only when both inputs 78 | are filled manually 79 | */ 80 | if (firstAmount === '' || secondAmount === '') { 81 | return true; 82 | } 83 | return false; 84 | }; 85 | 86 | function setActiveTab(type: string) { 87 | if (firstTabSelected === false && secondTabSelected === false) { 88 | if (type === 'First') { 89 | setFirstTabSelected(true); 90 | } 91 | if (type === 'Second') { 92 | setSecondTabSelected(true); 93 | } 94 | } else if (firstTabSelected === true) { 95 | if (type === 'Second') { 96 | setFirstTabSelected(false); 97 | setSecondTabSelected(true); 98 | } 99 | } else { 100 | if (type === 'First') { 101 | setFirstTabSelected(true); 102 | setSecondTabSelected(false); 103 | } 104 | } 105 | } 106 | 107 | return ( 108 |
109 |
110 | Create a Pair 111 | 112 | 115 | 116 |
117 |
118 | setFirstAmount(amount)} 122 | tokenList={tokenList} 123 | token={firstToken} 124 | updateToken={token => { 125 | dispatch(setFirstToken(token)); 126 | updateTokens(token, secondToken); 127 | }} 128 | active={firstTabSelected} 129 | onClick={() => setActiveTab('First')} 130 | /> 131 |

+

132 | setSecondAmount(amount)} 136 | tokenList={tokenList} 137 | token={secondToken} 138 | updateToken={token => { 139 | if (firstToken === '') { 140 | dispatch(setFirstToken(tokenList[0][0])); 141 | dispatch(setSecondToken(token)); 142 | updateTokens(tokenList[0][0], token); 143 | } else { 144 | dispatch(setSecondToken(token)); 145 | updateTokens(firstToken, token); 146 | } 147 | }} 148 | active={secondTabSelected} 149 | onClick={() => setActiveTab('Second')} 150 | /> 151 |
152 |
153 | {walletAddr ? ( 154 | 165 | ) : ( 166 | 169 | )} 170 |
171 | 172 | 176 | {firstToken && secondToken && ( 177 | 185 | )} 186 |
187 | ); 188 | }; 189 | 190 | export default CreatePair; 191 | -------------------------------------------------------------------------------- /frontend/src/components/CreatePairModal.scss: -------------------------------------------------------------------------------- 1 | .CreatePair-modal { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: flex-start; 6 | 7 | .CreatePair-modal-header { 8 | display: flex; 9 | padding: 10px; 10 | font-size: 18px; 11 | border-bottom: 1px solid lightgray; 12 | font-weight: 700; 13 | .CreatePair-modal-header-image { 14 | display: flex; 15 | align-items: center; 16 | margin-right: 5px; 17 | } 18 | } 19 | 20 | .CreatePair-modal-supply-summary { 21 | display: flex; 22 | flex-direction: column; 23 | 24 | .CreatePair-modal-supply-summary-info { 25 | text-align: left; 26 | font-size: 20px; 27 | padding: 5px; 28 | } 29 | } 30 | 31 | .CreatePair-modal-subtitle { 32 | font-size: 12px; 33 | font-style: italic; 34 | color: gray; 35 | font-weight: 700; 36 | margin-top: 15px; 37 | padding: 15px; 38 | } 39 | 40 | .CreatePair-modal-supply-details { 41 | display: flex; 42 | flex-direction: column; 43 | font-size: 20px; 44 | padding: 20px; 45 | margin-top: 10px; 46 | border-radius: 12px; 47 | border: 2px solid #a30786; 48 | .CreatePair-modal-supply-details-info { 49 | display: flex; 50 | justify-content: space-between; 51 | padding: 5px; 52 | } 53 | 54 | .CreatePair-modal-bottom { 55 | margin-top: 25px; 56 | } 57 | } 58 | 59 | .CreatePair-modal-bottom { 60 | margin-top: 25px; 61 | 62 | .CreatePair-modal-button { 63 | background-color: #a30786; 64 | color: #ffffff; 65 | padding: 10px 15px; 66 | width: 100%; 67 | height: 100%; 68 | font-size: 18px; 69 | border: 1px solid #a30786; 70 | border-radius: 12px; 71 | text-transform: uppercase; 72 | font-weight: 600; 73 | cursor: pointer; 74 | 75 | &:hover { 76 | background-color: #940678; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /frontend/src/components/CreatePairModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Need to disable typescript check to outsmart Rodal package issue. 3 | If you are making any changes to the code, remove this line temporarily 4 | as we want to pass typecheck testing as much as possible. 5 | */ 6 | // @ts-nocheck 7 | import React from 'react'; 8 | import {useSelector} from 'react-redux'; 9 | 10 | import {selectSlippageTolerance} from '../redux/reducers/transaction'; 11 | 12 | /* eslint-disable */ 13 | import Rodal from 'rodal'; 14 | import 'rodal/lib/rodal.css'; 15 | 16 | import './CreatePairModal.scss'; 17 | 18 | interface Props { 19 | firstAmount: string; 20 | firstToken: string; 21 | secondAmount: string; 22 | secondToken: string; 23 | 24 | openSupplyModal: boolean; 25 | toggleSupplyModal: () => void; 26 | } 27 | 28 | const CreatePairModal: React.FC = ({ 29 | firstAmount, 30 | firstToken, 31 | secondAmount, 32 | secondToken, 33 | openSupplyModal, 34 | toggleSupplyModal, 35 | }) => { 36 | const slippageTolerance = useSelector(selectSlippageTolerance); 37 | 38 | const modalStyle = { 39 | position: 'relative', 40 | borderRadius: '30px', 41 | top: '210px', 42 | }; 43 | 44 | return ( 45 | 53 |
54 |
55 |
56 | AlgoSwap 57 |
58 | Confirm Supply 59 |
60 |
61 | Supply Summary 62 | {parseFloat(firstAmount) / parseFloat(secondAmount)} 63 | 64 | {firstToken}/{secondToken} Pool Tokens 65 | 66 |
67 |
68 | Output is estimated. If the price changes by more than {slippageTolerance}%, your 69 | transaction will revert. 70 |
71 |
72 |
73 | {firstToken} Deposited 74 | {firstAmount} 75 |
76 |
77 | {secondToken} Deposited 78 | {secondAmount} 79 |
80 |
81 |
82 | 85 |
86 |
87 |
88 | ); 89 | }; 90 | 91 | export default CreatePairModal; 92 | -------------------------------------------------------------------------------- /frontend/src/components/LiquidityList.css: -------------------------------------------------------------------------------- 1 | .LiquidityList { 2 | display: flex; 3 | flex-direction: column; 4 | max-width: 640px; 5 | width: 100%; 6 | } 7 | 8 | .LiquidityList-header { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | } 13 | 14 | .LiquidityList-title { 15 | margin: 0; 16 | } 17 | 18 | .LiquidityList-buttons { 19 | display: flex; 20 | align-items: center; 21 | } 22 | 23 | .LiquidityList-create-pair { 24 | margin-right: 8px; 25 | background-color: transparent; 26 | color: #1e52ac; 27 | padding: 8px 16px; 28 | border: 1px solid #1e52ac; 29 | border-radius: 8px; 30 | cursor: pointer; 31 | } 32 | 33 | .LiquidityList-add-liquidity { 34 | background-color: #1e52ac; 35 | color: #ffffff; 36 | padding: 8px 16px; 37 | border: 1px solid #1e52ac; 38 | border-radius: 8px; 39 | cursor: pointer; 40 | } 41 | 42 | .LiquidityList-content { 43 | border: 1px solid #888888; 44 | border-radius: 8px; 45 | margin-top: 24px; 46 | } 47 | 48 | .LiquidityList-nullstate { 49 | color: #888888; 50 | font-size: 16px; 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/components/LiquidityList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router-dom'; 3 | 4 | import './LiquidityList.css'; 5 | 6 | export default class LiquidityList extends React.PureComponent { 7 | constructor(props: Props) { 8 | super(props); 9 | this.state = { 10 | liquidities: [], 11 | }; 12 | } 13 | 14 | renderLiquidities = () => { 15 | return this.state.liquidities.map(liquidity => ( 16 |
{liquidity}
17 | )); 18 | }; 19 | 20 | render() { 21 | return ( 22 |
23 |
24 |

Your liquidity

25 |
26 | 27 | 30 | 31 | 32 | 35 | 36 |
37 |
38 |
39 | {this.state.liquidities.length > 0 ? ( 40 | this.renderLiquidities() 41 | ) : ( 42 |

No liquidity found.

43 | )} 44 |
45 |
46 | ); 47 | } 48 | } 49 | 50 | interface Props {} 51 | interface State { 52 | liquidities: string[]; 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/components/NavigationBar.scss: -------------------------------------------------------------------------------- 1 | .Navbar { 2 | display: flex; 3 | justify-content: space-between; 4 | border-bottom: 2px solid lightgray; 5 | color: black; 6 | height: 55px; 7 | 8 | .Navbar-left, 9 | .Navbar-right { 10 | display: flex; 11 | align-items: center; 12 | 13 | .Navbar-container { 14 | display: inherit; 15 | } 16 | .Navbar-container-active, 17 | .Navbar-container:hover { 18 | display: inherit; 19 | font-weight: 600; 20 | border-bottom: 2px solid #a30786; 21 | } 22 | 23 | a { 24 | margin: 0 8px; 25 | color: black; 26 | text-decoration: none; 27 | font-size: 18px; 28 | } 29 | } 30 | 31 | .Navbar-right { 32 | padding-right: 10px; 33 | } 34 | } 35 | .App-logo-nav { 36 | padding-left: 10px; 37 | margin-right: 8px; 38 | height: 16px; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/components/NavigationBar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useSelector} from 'react-redux'; 3 | 4 | import {selectUserAccountAddress} from '../redux/reducers/user'; 5 | import {Link, useLocation} from 'react-router-dom'; 6 | 7 | import './NavigationBar.scss'; 8 | 9 | const NavigationBar: React.FC = () => { 10 | const {pathname} = useLocation(); 11 | const accountAddr = useSelector(selectUserAccountAddress); 12 | 13 | return ( 14 | 40 | ); 41 | }; 42 | 43 | export default NavigationBar; 44 | -------------------------------------------------------------------------------- /frontend/src/components/SwapComponent/SwapComponent.scss: -------------------------------------------------------------------------------- 1 | .SwapComponent { 2 | position: relative; 3 | margin: auto; 4 | width: 100%; 5 | max-width: 420px; 6 | border-radius: 30px; 7 | border: 2px solid lightgray; 8 | padding: 20px 1.5rem 20px 1.5rem; 9 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.16); 10 | 11 | .SwapComponent-header { 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | margin-bottom: 20px; 16 | font-size: 20px; 17 | font-weight: 600; 18 | } 19 | 20 | .SwapComponent-arrow { 21 | font-size: 16px; 22 | } 23 | 24 | .SwapComponent-info { 25 | display: flex; 26 | justify-content: space-between; 27 | font-size: 16px; 28 | font-weight: 600; 29 | color: gray; 30 | margin-top: 25px; 31 | padding: 0px 20px; 32 | } 33 | 34 | .SwapComponent-bottom { 35 | margin-top: 25px; 36 | 37 | .SwapComponent-button { 38 | background-color: #a30786; 39 | color: #ffffff; 40 | padding: 10px 15px; 41 | width: 100%; 42 | height: 100%; 43 | font-size: 18px; 44 | border: 1px solid #a30786; 45 | border-radius: 12px; 46 | text-transform: uppercase; 47 | font-weight: 600; 48 | cursor: pointer; 49 | 50 | &:hover { 51 | background-color: #940678; 52 | } 53 | } 54 | 55 | .SwapComponent-button-disabled { 56 | background-color: lightgray !important; 57 | border: 1px solid lightgray !important; 58 | } 59 | } 60 | 61 | .Settings-button { 62 | background-color: transparent; 63 | border: transparent; 64 | outline: none; 65 | 66 | .Settings-logo { 67 | height: 25px; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /frontend/src/components/SwapComponent/SwapComponent.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {useSelector, useDispatch} from 'react-redux'; 3 | 4 | import {selectUserAccountAddress} from '../../redux/reducers/user'; 5 | import {selectSlippageTolerance} from '../../redux/reducers/transaction'; 6 | import {selectFromToken, selectToToken, selectTokenList} from '../../redux/reducers/tokens'; 7 | import {setTokenList, setFromToken, setToToken} from '../../redux/actions'; 8 | 9 | import TokenAmount from '../TokenAmount/TokenAmount'; 10 | import SwapModal from './SwapModal'; 11 | import SettingsModal from '../common/SettingsModal'; 12 | import WalletModal from '../common/WalletModal'; 13 | 14 | import {allPairDetails} from "../../constants"; 15 | 16 | import './SwapComponent.scss'; 17 | 18 | const SwapComponent: React.FC = () => { 19 | // Local state 20 | const [fromAmount, setFromAmount] = useState(''); 21 | const [toAmount, setToAmount] = useState(''); 22 | 23 | const [fromTabSelected, setFromTabSelected] = useState(false); 24 | const [toTabSelected, setToTabSelected] = useState(false); 25 | 26 | // Modals 27 | const [openWalletModal, setOpenWalletModal] = useState(false); 28 | const [openSettingsModal, setOpenSettingsModal] = useState(false); 29 | const [openSwapModal, setOpenSwapModal] = useState(false); 30 | 31 | // Redux state 32 | const walletAddr = useSelector(selectUserAccountAddress); 33 | const slippageTolerance = useSelector(selectSlippageTolerance); 34 | const tokenList = useSelector(selectTokenList); 35 | const fromToken = useSelector(selectFromToken); 36 | const toToken = useSelector(selectToToken); 37 | 38 | const dispatch = useDispatch(); 39 | 40 | const onFirstRender = () => { 41 | dispatch( 42 | setTokenList([ 43 | ['ETH', '0'], 44 | ['BTC', '1'], 45 | ['ALG', '2'], 46 | ['USD', '3'], 47 | ]) 48 | ); 49 | }; 50 | 51 | useEffect(() => { 52 | onFirstRender(); 53 | }, []); 54 | 55 | const toggleWalletModal = () => { 56 | setOpenWalletModal(!openWalletModal); 57 | }; 58 | 59 | const toggleSettingsModal = () => { 60 | setOpenSettingsModal(!openSettingsModal); 61 | }; 62 | 63 | const toggleSwapModal = () => { 64 | setOpenSwapModal(!openSwapModal); 65 | }; 66 | 67 | const bothTokensNotSet = () => { 68 | if (fromToken === undefined || toToken === undefined) { 69 | return true; 70 | } 71 | /* 72 | TODO: 73 | If one of the input panels are given the value, the other 74 | one should be automatically calculated using the conversion rate. 75 | 76 | For now, the swap button is clickable only when both inputs 77 | are filled manually 78 | */ 79 | if (fromAmount === '' || toAmount === '') { 80 | return true; 81 | } 82 | return false; 83 | }; 84 | 85 | const setActiveTab = (type: string) => { 86 | if (fromTabSelected === false && toTabSelected === false) { 87 | if (type === 'From') { 88 | setFromTabSelected(true); 89 | } 90 | if (type === 'To') { 91 | setToTabSelected(true); 92 | } 93 | } else if (fromTabSelected === true) { 94 | if (type === 'To') { 95 | setFromTabSelected(false); 96 | setToTabSelected(true); 97 | } 98 | } else { 99 | if (type === 'From') { 100 | setFromTabSelected(true); 101 | setToTabSelected(false); 102 | } 103 | } 104 | }; 105 | 106 | return ( 107 |
108 |
109 | Swap 110 | 111 | 114 | 115 |
116 |
117 | setFromAmount(amount)} 121 | tokenList={tokenList} 122 | token={fromToken || ''} 123 | updateToken={token => dispatch(setFromToken(token))} 124 | active={fromTabSelected} 125 | onClick={() => setActiveTab('From')} 126 | /> 127 |

128 | setToAmount(amount)} 132 | tokenList={tokenList} 133 | token={toToken || ''} 134 | updateToken={token => dispatch(setToToken(token))} 135 | active={toTabSelected} 136 | onClick={() => setActiveTab('To')} 137 | /> 138 |
139 |
140 | Slippage Tolerance: 141 | {slippageTolerance.toFixed(2)}% 142 |
143 |
144 | {walletAddr ? ( 145 | 156 | ) : ( 157 | 160 | )} 161 |
162 | 163 | 167 | {fromToken && toToken && ( 168 | 176 | )} 177 |
178 | ); 179 | }; 180 | 181 | export default SwapComponent; 182 | -------------------------------------------------------------------------------- /frontend/src/components/SwapComponent/SwapModal.scss: -------------------------------------------------------------------------------- 1 | .SwapComponent-swap-modal { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: flex-start; 6 | 7 | .SwapComponent-swap-modal-header { 8 | display: flex; 9 | padding: 10px; 10 | font-size: 18px; 11 | border-bottom: 1px solid lightgray; 12 | font-weight: 700; 13 | .SwapComponent-swap-modal-header-image { 14 | display: flex; 15 | align-items: center; 16 | margin-right: 5px; 17 | } 18 | } 19 | 20 | .SwapComponent-swap-modal-txdetails { 21 | display: flex; 22 | flex-direction: column; 23 | margin-top: 15px; 24 | 25 | .SwapComponent-swap-modal-txdetail { 26 | display: flex; 27 | flex-direction: row; 28 | justify-content: space-between; 29 | font-size: 24px; 30 | font-weight: 700; 31 | padding: 0px 20px; 32 | } 33 | 34 | .SwapComponent-arrow { 35 | font-size: 16px; 36 | } 37 | 38 | .SwapComponent-swap-modal-subtitle { 39 | font-size: 12px; 40 | font-style: italic; 41 | color: gray; 42 | font-weight: 700; 43 | margin-top: 15px; 44 | padding: 15px; 45 | } 46 | 47 | .SwapComponent-swap-modal-txinfo { 48 | display: flex; 49 | justify-content: space-between; 50 | font-size: 20px !important; 51 | padding: 20px; 52 | margin-top: 10px; 53 | border-radius: 12px; 54 | border: 2px solid #a30786; 55 | } 56 | } 57 | 58 | .SwapComponent-swap-modal-bottom { 59 | margin-top: 25px; 60 | 61 | .SwapComponent-swap-modal-button { 62 | background-color: #a30786; 63 | color: #ffffff; 64 | padding: 10px 15px; 65 | width: 100%; 66 | height: 100%; 67 | font-size: 18px; 68 | border: 1px solid #a30786; 69 | border-radius: 12px; 70 | text-transform: uppercase; 71 | font-weight: 600; 72 | cursor: pointer; 73 | 74 | &:hover { 75 | background-color: #940678; 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /frontend/src/components/SwapComponent/SwapModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Need to disable typescript check to outsmart Rodal package issue. 3 | If you are making any changes to the code, remove this line temporarily 4 | as we want to pass typecheck testing as much as possible. 5 | */ 6 | // @ts-nocheck 7 | import React from 'react'; 8 | 9 | /* eslint-disable */ 10 | import Rodal from 'rodal'; 11 | import 'rodal/lib/rodal.css'; 12 | 13 | import './SwapModal.scss'; 14 | import swapToken1ForToken2 from "../../services/swapToken1ForToken2"; 15 | 16 | interface Props { 17 | fromAmount: string; 18 | toAmount: string; 19 | fromToken: string; 20 | toToken: string; 21 | 22 | openSwapModal: boolean; 23 | toggleSwapModal: () => void; 24 | } 25 | 26 | const SwapModal: React.FC = ({ 27 | fromAmount, 28 | toAmount, 29 | fromToken, 30 | toToken, 31 | openSwapModal, 32 | toggleSwapModal, 33 | }) => { 34 | const modalStyle = { 35 | position: 'relative', 36 | borderRadius: '30px', 37 | top: '210px', 38 | }; 39 | 40 | const tokenSwap = () => { 41 | console.log("onClick"); 42 | swapToken1ForToken2("WB3WP73CEBEXRCZWQY2OE2FY6KVHTXSLG5NRNZVXTP4AWSSAFGO6N3PD7U", "R3CB4EH5HBPWU5DT7QUFSXFNP44QFAUZYCVKCQIPIPTGPTCMHQC2JIOAVA", 10000000, 14973863, 8088850).then(() => { 43 | console.log("Sent txns"); 44 | }).catch(e => { 45 | console.error(e); 46 | }) 47 | } 48 | 49 | return ( 50 | 58 |
59 |
60 |
61 | AlgoSwap 62 |
63 | Confirm Swap 64 |
65 |
66 |
67 | 68 | AlgoSwap 69 | {fromAmount} 70 | 71 | {fromToken} 72 |
73 |

74 |
75 | 76 | AlgoSwap 77 | {toAmount} 78 | 79 | {toToken} 80 |
81 |
82 | Output is estimated. You will receive at least {toAmount} {toToken} or the transaction 83 | will revert. 84 |
85 |
86 | Price: 87 | 88 | {parseFloat(toAmount) / parseFloat(fromAmount)} {toToken}/{fromToken} 89 | 90 |
91 |
92 |
93 | 96 |
97 |
98 |
99 | ); 100 | }; 101 | 102 | export default SwapModal; 103 | -------------------------------------------------------------------------------- /frontend/src/components/TokenAmount/TokenAmount.scss: -------------------------------------------------------------------------------- 1 | .TokenAmount { 2 | display: flex; 3 | flex-direction: column; 4 | border: 2px solid lightgray; 5 | border-radius: 12px; 6 | padding: 16px; 7 | 8 | .TokenAmount-title { 9 | margin: 0 0 12px 0; 10 | font-size: 16px; 11 | text-align: left; 12 | } 13 | 14 | .TokenAmount-content { 15 | display: flex; 16 | justify-content: space-between; 17 | align-items: center; 18 | } 19 | 20 | .TokenAmount-amount { 21 | background-color: transparent; 22 | font-size: 24px; 23 | border: none; 24 | outline: none; 25 | } 26 | 27 | .TokenAmount-token-select { 28 | background-color: transparent; 29 | background-repeat: no-repeat; 30 | border: none; 31 | border-radius: 12px; 32 | outline: none; 33 | cursor: pointer; 34 | font-size: 16px; 35 | font-weight: 600; 36 | padding: 0 1rem; 37 | height: 2.2rem; 38 | user-select: none; 39 | 40 | &:hover { 41 | background-color: whitesmoke; 42 | } 43 | } 44 | 45 | &:hover { 46 | border: 2px solid #a30786 !important; 47 | } 48 | } 49 | 50 | .TokenAmount-active { 51 | border: 2px solid #a30786 !important; 52 | font-weight: 700; 53 | } 54 | 55 | input[type='number']::-webkit-inner-spin-button, 56 | input[type='number']::-webkit-outer-spin-button { 57 | -webkit-appearance: none; 58 | margin: 0; 59 | } 60 | -------------------------------------------------------------------------------- /frontend/src/components/TokenAmount/TokenAmount.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import TokenModal from './TokenModal'; 3 | 4 | import './TokenAmount.scss'; 5 | 6 | interface Props { 7 | title: string; 8 | amount: string; 9 | tokenList?: Array>; 10 | token: string; 11 | updateAmount: (amount: string) => void; 12 | updateToken: (token: string) => void; 13 | active: boolean; 14 | onClick: () => void; 15 | } 16 | 17 | const TokenAmount: React.FC = ({ 18 | title, 19 | amount, 20 | updateAmount, 21 | tokenList, 22 | token, 23 | updateToken, 24 | active, 25 | onClick, 26 | }) => { 27 | const [selectedToken, setSelectedToken] = useState(token); 28 | const [openTokenModal, setOpenTokenModal] = useState(false); 29 | 30 | useEffect(() => { 31 | setSelectedToken(token); 32 | }, [token]); 33 | 34 | const toggleTokenModal = () => { 35 | setOpenTokenModal(!openTokenModal); 36 | token !== selectedToken && updateToken(selectedToken); 37 | }; 38 | 39 | return ( 40 |
44 |

{title}

45 |
46 | updateAmount(evt.target.value)} 51 | value={amount} 52 | /> 53 | 56 |
57 | setSelectedToken(token)} 61 | openTokenModal={openTokenModal} 62 | toggleTokenModal={toggleTokenModal} 63 | /> 64 |
65 | ); 66 | }; 67 | 68 | export default TokenAmount; 69 | -------------------------------------------------------------------------------- /frontend/src/components/TokenAmount/TokenModal.scss: -------------------------------------------------------------------------------- 1 | .TokenAmount-token-modal { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: flex-start; 6 | 7 | .TokenAmount-token-modal-header { 8 | display: flex; 9 | padding: 10px; 10 | font-size: 18px; 11 | border-bottom: 1px solid lightgray; 12 | .TokenAmount-token-modal-header-image { 13 | display: flex; 14 | align-items: center; 15 | margin-right: 5px; 16 | } 17 | } 18 | .TokenAmount-token-modal-list { 19 | flex: 1; 20 | overflow-y: auto; 21 | .TokenAmount-token { 22 | display: flex; 23 | border: 2px solid transparent; 24 | border-radius: 12px; 25 | font-size: 24px; 26 | padding: 10px; 27 | margin: 5px; 28 | 29 | &:hover { 30 | border: 2px solid #a30786; 31 | } 32 | } 33 | 34 | .TokenAmount-token-active { 35 | background-color: whitesmoke; 36 | } 37 | } 38 | 39 | .TokenAmount-token-modal-footer { 40 | border-top: 1px solid lightgray; 41 | .TokenAmount-token-modal-close-button { 42 | background-color: #a30786; 43 | color: #ffffff; 44 | padding: 10px 15px; 45 | margin: 10px; 46 | font-size: 18px; 47 | border: 1px solid #a30786; 48 | border-radius: 12px; 49 | text-transform: uppercase; 50 | font-weight: 600; 51 | 52 | &:hover { 53 | background-color: #940678; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /frontend/src/components/TokenAmount/TokenModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Need to disable typescript check to outsmart Rodal package issue. 3 | If you are making any changes to the code, remove this line temporarily 4 | as we want to pass typecheck testing as much as possible. 5 | */ 6 | // @ts-nocheck 7 | import React from 'react'; 8 | 9 | /* eslint-disable */ 10 | import Rodal from 'rodal'; 11 | import 'rodal/lib/rodal.css'; 12 | 13 | import './TokenModal.scss'; 14 | 15 | interface Props { 16 | selectedToken: string; 17 | tokenList?: Array>; 18 | 19 | updateSelectedToken: (token: string) => void; 20 | openTokenModal: boolean; 21 | toggleTokenModal: () => void; 22 | } 23 | 24 | const TokenModal: React.FC = ({ 25 | selectedToken, 26 | tokenList, 27 | updateSelectedToken, 28 | openTokenModal, 29 | toggleTokenModal, 30 | }) => { 31 | const modalStyle = { 32 | position: 'relative', 33 | 'border-radius': '30px', 34 | top: '210px', 35 | }; 36 | 37 | return ( 38 | 45 |
46 |
47 |
48 | AlgoSwap 49 |
50 | Select a token 51 |
52 |
53 | {tokenList && 54 | tokenList.map(token => { 55 | if (token[0] === selectedToken) { 56 | return ( 57 |
updateSelectedToken(token[0])} 61 | > 62 | {token[0]} 63 |
64 | ); 65 | } 66 | return ( 67 |
updateSelectedToken(token[0])} 71 | > 72 | {token[0]} 73 |
74 | ); 75 | })} 76 |
77 |
78 | 81 |
82 |
83 |
84 | ); 85 | }; 86 | 87 | export default TokenModal; 88 | -------------------------------------------------------------------------------- /frontend/src/components/common/SettingsModal.scss: -------------------------------------------------------------------------------- 1 | .Settings-modal { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: flex-start; 5 | height: 100%; 6 | font-size: 1rem; 7 | 8 | .Settings-modal-header { 9 | display: flex; 10 | padding: 10px; 11 | font-size: 18px; 12 | border-bottom: 1px solid lightgray; 13 | font-weight: 700; 14 | } 15 | 16 | .Settings-modal-slippage { 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | margin: 15px; 21 | 22 | .Settings-modal-slippage-content { 23 | .Settings-modal-slippage-input { 24 | border: transparent; 25 | outline: none; 26 | font-size: 16px; 27 | text-align: right; 28 | } 29 | } 30 | } 31 | 32 | .Settings-modal-info { 33 | border-radius: 12px; 34 | border: 2px solid #a30786; 35 | margin-top: 15px; 36 | padding: 10px; 37 | text-align: left; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/components/common/SettingsModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Need to disable typescript check to outsmart Rodal package issue. 3 | If you are making any changes to the code, remove this line temporarily 4 | as we want to pass typecheck testing as much as possible. 5 | */ 6 | // @ts-nocheck 7 | import React from 'react'; 8 | import {useSelector, useDispatch} from 'react-redux'; 9 | 10 | import {selectSlippageTolerance} from '../../redux/reducers/transaction'; 11 | import {setSlippageTolerance} from '../../redux/actions'; 12 | 13 | /* eslint-disable */ 14 | import Rodal from 'rodal'; 15 | import 'rodal/lib/rodal.css'; 16 | 17 | import './SettingsModal.scss'; 18 | 19 | interface Props { 20 | openSettingsModal: boolean; 21 | toggleSettingsModal: () => void; 22 | } 23 | 24 | const SettingsModal: React.FC = ({openSettingsModal, toggleSettingsModal}) => { 25 | const slippageTolerance = useSelector(selectSlippageTolerance); 26 | const dispatch = useDispatch(); 27 | 28 | const modalStyle = { 29 | position: 'relative', 30 | borderRadius: '30px', 31 | top: '290px', 32 | }; 33 | 34 | const updateSlippageTolerance = (value: string) => { 35 | const converted = parseFloat(value); 36 | !isNaN(converted) 37 | ? dispatch(setSlippageTolerance(converted)) 38 | : dispatch(setSlippageTolerance(1)); 39 | }; 40 | 41 | return ( 42 | 50 |
51 |
Transaction Settings
52 |
53 | Slippage Tolerance 54 |
55 | updateSlippageTolerance(evt.target.value)} 60 | /> 61 | % 62 |
63 |
64 |
65 | The transaction will revert if the price changes by more than this percentage. 66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default SettingsModal; 73 | -------------------------------------------------------------------------------- /frontend/src/components/common/WalletModal.scss: -------------------------------------------------------------------------------- 1 | .Wallet-modal { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: flex-start; 5 | height: 100%; 6 | 7 | .Wallet-modal-header { 8 | display: flex; 9 | padding: 10px; 10 | font-size: 18px; 11 | border-bottom: 1px solid lightgray; 12 | font-weight: 700; 13 | .Wallet-modal-header-image { 14 | display: flex; 15 | align-items: center; 16 | margin-right: 5px; 17 | } 18 | } 19 | 20 | .Wallet-modal-select { 21 | background-color: transparent; 22 | border: 2px solid lightgray; 23 | margin: 15px; 24 | border-radius: 12px; 25 | font-size: 18px; 26 | font-weight: 700; 27 | 28 | .Wallet-modal-item { 29 | display: flex; 30 | justify-content: space-between; 31 | align-items: center; 32 | padding: 15px; 33 | } 34 | 35 | &:hover { 36 | border: 2px solid #a30786; 37 | } 38 | } 39 | 40 | .Wallet-logo-modal { 41 | height: 45px; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/components/common/WalletModal.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | Need to disable typescript check to outsmart Rodal package issue. 3 | If you are making any changes to the code, remove this line temporarily 4 | as we want to pass typecheck testing as much as possible. 5 | */ 6 | //@ts-nocheck 7 | import React from 'react'; 8 | import {useDispatch} from 'react-redux'; 9 | 10 | import {setAccountAddress} from '../../redux/actions'; 11 | 12 | /* eslint-disable */ 13 | import Rodal from 'rodal'; 14 | import 'rodal/lib/rodal.css'; 15 | 16 | import './WalletModal.scss'; 17 | import {calculateUnused} from '../../services/helpers'; 18 | 19 | interface Props { 20 | openWalletModal: boolean; 21 | toggleWalletModal: () => void; 22 | } 23 | 24 | const WalletModal: React.FC = ({openWalletModal, toggleWalletModal}) => { 25 | const dispatch = useDispatch(); 26 | 27 | const modalStyle = { 28 | position: 'relative', 29 | borderRadius: '30px', 30 | top: '210px', 31 | }; 32 | 33 | async function connectToAlgoSigner() { 34 | if (typeof AlgoSigner !== 'undefined') { 35 | try { 36 | await AlgoSigner.connect(); 37 | let testnetAccounts = await AlgoSigner.accounts({ 38 | ledger: 'TestNet', 39 | }); 40 | dispatch(setAccountAddress(testnetAccounts[0].address)); 41 | toggleWalletModal(); 42 | await calculateUnused( 43 | testnetAccounts[0].address, 44 | 'SDJ5WZSZXRQK6YQUXDTKUXWGWX23DNQM62TCB2Z2WDAISGPUQZG6LB6TJM', 45 | 'U1' 46 | ); 47 | await calculateUnused( 48 | testnetAccounts[0].address, 49 | 'SDJ5WZSZXRQK6YQUXDTKUXWGWX23DNQM62TCB2Z2WDAISGPUQZG6LB6TJM', 50 | 'U2' 51 | ); 52 | await calculateUnused( 53 | testnetAccounts[0].address, 54 | 'SDJ5WZSZXRQK6YQUXDTKUXWGWX23DNQM62TCB2Z2WDAISGPUQZG6LB6TJM', 55 | 'UL' 56 | ); 57 | } catch (e) { 58 | console.error(e); 59 | } 60 | } 61 | } 62 | 63 | return ( 64 | 72 |
73 |
74 |
75 | AlgoSwap 76 |
77 | Connect to a wallet 78 |
79 | 85 |
86 |
87 | ); 88 | }; 89 | 90 | export default WalletModal; 91 | -------------------------------------------------------------------------------- /frontend/src/config.js: -------------------------------------------------------------------------------- 1 | const developmentConfig = require('./config/development'); 2 | const productionConfig = require('./config/production'); 3 | 4 | const isProduction = process.env.ALGORAND_IS_PRODUCTION || false; 5 | 6 | module.exports = { 7 | config: isProduction ? productionConfig : developmentConfig, 8 | isProduction, 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/config/development.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | API_KEY: 'LGG679KlpG3kSNMWRpbr48XhlCUknSEC7gO0d1I5', 3 | BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/config/production.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | API_KEY: '', 3 | BASE_SERVER: '', 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/constants.ts: -------------------------------------------------------------------------------- 1 | import PairDetails from "./models/PairDetails"; 2 | 3 | export const allPairDetails: PairDetails[] = [ 4 | { 5 | token1Name: "Testnet Token1", 6 | token1Symbol: "T1", 7 | token1Index: 14973863, 8 | token2Name: "Testnet Token2", 9 | token2Symbol: "T2", 10 | token2Index: 14973864, 11 | liquidityTokenName: "Liquidity Token1/Token2", 12 | liquidityTokenSymbol: "T1T2", 13 | liquidityTokenIndex: 14973866, 14 | escrowAddress: "R3CB4EH5HBPWU5DT7QUFSXFNP44QFAUZYCVKCQIPIPTGPTCMHQC2JIOAVA", 15 | escrowLogicSig: "AiALAQMEBqb3kQel95EHAuLBoAan95EHqPeRB6r3kQcyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=", 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | /* @font-face { 2 | font-family: 'Overpass 1'; 3 | src: url('./assets/fonts/1/Overpass-Thin.ttf') format('ttf'); 4 | } 5 | @font-face { 6 | font-family: 'Overpass 2'; 7 | src: url('./assets/fonts/2/Overpass-ExtraLight.ttf') format('ttf'); 8 | } 9 | @font-face { 10 | font-family: 'Overpass 3'; 11 | src: url('./assets/fonts/3/Overpass-Light.ttf') format('ttf'); 12 | } */ 13 | @font-face { 14 | font-family: 'Overpass 4'; 15 | src: url('./assets/fonts/4/Overpass-Regular.woff') format('woff'), 16 | url('./assets/fonts/4/Overpass-Regular.woff2') format('woff2'); 17 | } 18 | /* @font-face { 19 | font-family: 'Overpass 5'; 20 | src: url('./assets/fonts/5/Overpass-SemiBold.ttf') format('ttf'); 21 | } 22 | @font-face { 23 | font-family: 'Overpass 6'; 24 | src: url('./assets/fonts/6/Overpass-Bold.ttf') format('ttf'); 25 | } 26 | @font-face { 27 | font-family: 'Overpass 7'; 28 | src: url('./assets/fonts/7/Overpass-ExtraBold.ttf') format('ttf'); 29 | } 30 | @font-face { 31 | font-family: 'Overpass 8'; 32 | src: url('./assets/fonts/8/Overpass-Black.ttf') format('ttf'); 33 | } */ 34 | 35 | body { 36 | margin: 0; 37 | font-family: -apple-system, BlinkMacSystemFont, 'Overpass 4', 'Segoe UI', 'Roboto', 'Oxygen', 38 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 39 | -webkit-font-smoothing: antialiased; 40 | -moz-osx-font-smoothing: grayscale; 41 | } 42 | 43 | code { 44 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want your app to work offline and load faster, you can change 15 | // unregister() to register() below. Note this comes with some pitfalls. 16 | // Learn more about service workers: https://bit.ly/CRA-PWA 17 | serviceWorker.unregister(); 18 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/models/PairDetails.ts: -------------------------------------------------------------------------------- 1 | type PairDetails = { 2 | token1Name: string; 3 | token1Symbol: string; 4 | token1Index: number; 5 | token2Name: string; 6 | token2Symbol: string; 7 | token2Index: number; 8 | liquidityTokenName: string; 9 | liquidityTokenSymbol: string; 10 | liquidityTokenIndex: number; 11 | escrowAddress: string; 12 | escrowLogicSig: string; 13 | }; 14 | 15 | export default PairDetails; 16 | -------------------------------------------------------------------------------- /frontend/src/pages/AddPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useSelector, useDispatch} from 'react-redux'; 3 | 4 | import {useHistory, useParams} from 'react-router-dom'; 5 | import {selectFirstToken, selectSecondToken} from '../redux/reducers/tokens'; 6 | import {setFirstToken, setSecondToken} from '../redux/actions'; 7 | 8 | import AddLiquidity from '../components/AddLiquidity'; 9 | 10 | const AddPage: React.FC = () => { 11 | const history = useHistory(); 12 | const {first, second} = useParams(); 13 | const dispatch = useDispatch(); 14 | 15 | let firstToken = useSelector(selectFirstToken); 16 | let secondToken = useSelector(selectSecondToken); 17 | 18 | if (firstToken !== first) { 19 | dispatch(setFirstToken(first || '')); 20 | firstToken = first; 21 | } 22 | if (secondToken !== second) { 23 | dispatch(setSecondToken(second || '')); 24 | secondToken = second; 25 | } 26 | 27 | return ( 28 |
29 | 33 | (firstToken !== '' || secondToken !== '') && 34 | history.push(`/add/${firstToken}/${secondToken}`) 35 | } 36 | /> 37 |
38 | ); 39 | }; 40 | 41 | interface ParamTypes { 42 | first: string | undefined; 43 | second: string | undefined; 44 | } 45 | 46 | export default AddPage; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/CreatePage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useSelector, useDispatch} from 'react-redux'; 3 | 4 | import {useHistory, useParams} from 'react-router-dom'; 5 | import {selectFirstToken, selectSecondToken} from '../redux/reducers/tokens'; 6 | import {setFirstToken, setSecondToken} from '../redux/actions'; 7 | 8 | import CreatePair from '../components/CreatePair'; 9 | 10 | const CreatePage: React.FC = () => { 11 | const history = useHistory(); 12 | const {first, second} = useParams(); 13 | const dispatch = useDispatch(); 14 | 15 | let firstToken = useSelector(selectFirstToken); 16 | let secondToken = useSelector(selectSecondToken); 17 | 18 | if (firstToken !== first) { 19 | dispatch(setFirstToken(first || '')); 20 | firstToken = first; 21 | } 22 | if (secondToken !== second) { 23 | dispatch(setSecondToken(second || '')); 24 | secondToken = second; 25 | } 26 | 27 | return ( 28 |
29 | 33 | (firstToken !== '' || secondToken !== '') && 34 | history.push(`/create/${firstToken}/${secondToken}`) 35 | } 36 | /> 37 |
38 | ); 39 | }; 40 | 41 | interface ParamTypes { 42 | first: string | undefined; 43 | second: string | undefined; 44 | } 45 | 46 | export default CreatePage; 47 | -------------------------------------------------------------------------------- /frontend/src/pages/PoolPage.scss: -------------------------------------------------------------------------------- 1 | .PoolPage { 2 | width: 100%; 3 | display: flex; 4 | justify-content: center; 5 | padding: 64px 0; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/pages/PoolPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import LiquidityList from '../components/LiquidityList'; 4 | import './PoolPage.scss'; 5 | 6 | const PoolPage: React.FC = () => { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | }; 13 | 14 | export default PoolPage; 15 | -------------------------------------------------------------------------------- /frontend/src/pages/SwapPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SwapComponent from '../components/SwapComponent/SwapComponent'; 4 | 5 | const SwapPage: React.FC = () => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default SwapPage; 14 | -------------------------------------------------------------------------------- /frontend/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/src/redux/actions/index.ts: -------------------------------------------------------------------------------- 1 | import ActionType from './types'; 2 | 3 | export type CurrentAccountAddress = {type: ActionType.SetAccountAddress; accountAddress: string}; 4 | 5 | export type CurrentSlippageTolerance = { 6 | type: ActionType.SetSlippageTolerance; 7 | slippageTolerance: number; 8 | }; 9 | 10 | export type CurrentTokenList = {type: ActionType.SetTokenList; tokenList: Array>}; 11 | export type CurrentFromToken = {type: ActionType.SetFromToken; fromToken: string}; 12 | export type CurrentToToken = {type: ActionType.SetToToken; toToken: string}; 13 | export type CurrentFirstToken = {type: ActionType.SetFirstToken; firstToken: string}; 14 | export type CurrentSecondToken = {type: ActionType.SetSecondToken; secondToken: string}; 15 | 16 | export function setAccountAddress(accountAddress: string): CurrentAccountAddress { 17 | return { 18 | accountAddress, 19 | type: ActionType.SetAccountAddress, 20 | }; 21 | } 22 | 23 | export function setSlippageTolerance(slippageTolerance: number): CurrentSlippageTolerance { 24 | return { 25 | slippageTolerance, 26 | type: ActionType.SetSlippageTolerance, 27 | }; 28 | } 29 | 30 | export function setTokenList(tokenList: Array>): CurrentTokenList { 31 | return { 32 | tokenList, 33 | type: ActionType.SetTokenList, 34 | }; 35 | } 36 | 37 | export function setFromToken(fromToken: string): CurrentFromToken { 38 | return { 39 | fromToken, 40 | type: ActionType.SetFromToken, 41 | }; 42 | } 43 | 44 | export function setToToken(toToken: string): CurrentToToken { 45 | return { 46 | toToken, 47 | type: ActionType.SetToToken, 48 | }; 49 | } 50 | 51 | export function setFirstToken(firstToken: string): CurrentFirstToken { 52 | return { 53 | firstToken, 54 | type: ActionType.SetFirstToken, 55 | }; 56 | } 57 | 58 | export function setSecondToken(secondToken: string): CurrentSecondToken { 59 | return { 60 | secondToken, 61 | type: ActionType.SetSecondToken, 62 | }; 63 | } 64 | -------------------------------------------------------------------------------- /frontend/src/redux/actions/types.ts: -------------------------------------------------------------------------------- 1 | enum ActionType { 2 | SetAccountAddress = 'SET_ACCOUNT_ADDRESS', 3 | 4 | SetTokenList = 'SET_TOKEN_LIST', 5 | SetFromToken = 'SET_FROM_TOKEN', 6 | SetToToken = 'SET_TO_TOKEN', 7 | SetFirstToken = 'SET_FIRST_TOKEN', 8 | SetSecondToken = 'SET_SECOND_TOKEN', 9 | 10 | SetSlippageTolerance = 'SET_SLIPPAGE_TOLERANCE', 11 | } 12 | 13 | export default ActionType; 14 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | 3 | import user from './user'; 4 | import transaction from './transaction'; 5 | import tokens from './tokens'; 6 | 7 | export default combineReducers({ 8 | user, 9 | transaction, 10 | tokens, 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/tokens.ts: -------------------------------------------------------------------------------- 1 | import ActionType from '../actions/types'; 2 | import { 3 | CurrentTokenList, 4 | CurrentFromToken, 5 | CurrentToToken, 6 | CurrentFirstToken, 7 | CurrentSecondToken, 8 | } from '../actions'; 9 | import CurrentAlgoSwapContext, {CurrentTokenInfo} from './types'; 10 | 11 | const initTokenInfo: CurrentTokenInfo = { 12 | tokenList: [], 13 | fromToken: undefined, 14 | toToken: undefined, 15 | firstToken: undefined, 16 | secondToken: undefined, 17 | }; 18 | 19 | type CurrentTokenInfoAction = 20 | | CurrentTokenList 21 | | CurrentFromToken 22 | | CurrentToToken 23 | | CurrentFirstToken 24 | | CurrentSecondToken; 25 | 26 | export const selectFromToken = (state: CurrentAlgoSwapContext) => { 27 | return state.tokens.fromToken; 28 | }; 29 | 30 | export const selectToToken = (state: CurrentAlgoSwapContext) => { 31 | return state.tokens.toToken; 32 | }; 33 | 34 | export const selectFirstToken = (state: CurrentAlgoSwapContext) => { 35 | return state.tokens.firstToken; 36 | }; 37 | 38 | export const selectSecondToken = (state: CurrentAlgoSwapContext) => { 39 | return state.tokens.secondToken; 40 | }; 41 | 42 | export const selectTokenList = (state: CurrentAlgoSwapContext) => { 43 | return state.tokens.tokenList; 44 | }; 45 | 46 | export default function tokens( 47 | state = initTokenInfo, 48 | action: CurrentTokenInfoAction 49 | ): CurrentTokenInfo { 50 | switch (action.type) { 51 | case ActionType.SetTokenList: 52 | return {...state, tokenList: action.tokenList}; 53 | case ActionType.SetFromToken: 54 | return {...state, fromToken: action.fromToken}; 55 | case ActionType.SetToToken: 56 | return {...state, toToken: action.toToken}; 57 | case ActionType.SetFirstToken: 58 | return {...state, firstToken: action.firstToken}; 59 | case ActionType.SetSecondToken: 60 | return {...state, secondToken: action.secondToken}; 61 | default: 62 | return state; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/transaction.ts: -------------------------------------------------------------------------------- 1 | import ActionType from '../actions/types'; 2 | import {CurrentSlippageTolerance} from '../actions'; 3 | import CurrentAlgoSwapContext, {CurrentTransaction} from './types'; 4 | 5 | const initTransaction: CurrentTransaction = { 6 | slippageTolerance: 1, 7 | }; 8 | 9 | export const selectSlippageTolerance = (state: CurrentAlgoSwapContext) => { 10 | return state.transaction.slippageTolerance; 11 | }; 12 | 13 | export default function transaction( 14 | state = initTransaction, 15 | action: CurrentSlippageTolerance 16 | ): CurrentTransaction { 17 | switch (action.type) { 18 | case ActionType.SetSlippageTolerance: 19 | return {...state, slippageTolerance: action.slippageTolerance}; 20 | default: 21 | return state; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/types.ts: -------------------------------------------------------------------------------- 1 | export interface CurrentUser { 2 | readonly accountAddress: string | null; 3 | } 4 | 5 | export interface CurrentTransaction { 6 | readonly slippageTolerance: number; 7 | } 8 | 9 | export interface CurrentTokenInfo { 10 | readonly tokenList: Array> | []; 11 | readonly fromToken: string | undefined; 12 | readonly toToken: string | undefined; 13 | readonly firstToken: string | undefined; 14 | readonly secondToken: string | undefined; 15 | } 16 | 17 | export default interface CurrentAlgoSwapContext { 18 | user: CurrentUser; 19 | transaction: CurrentTransaction; 20 | tokens: CurrentTokenInfo; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/redux/reducers/user.ts: -------------------------------------------------------------------------------- 1 | import ActionType from '../actions/types'; 2 | import {CurrentAccountAddress} from '../actions'; 3 | import CurrentAlgoSwapContext, {CurrentUser} from './types'; 4 | 5 | const initUser: CurrentUser = { 6 | accountAddress: null, 7 | }; 8 | 9 | export const selectUserAccountAddress = (state: CurrentAlgoSwapContext) => { 10 | return state.user.accountAddress; 11 | }; 12 | 13 | export default function user(state = initUser, action: CurrentAccountAddress): CurrentUser { 14 | switch (action.type) { 15 | case ActionType.SetAccountAddress: 16 | return {...state, accountAddress: action.accountAddress}; 17 | default: 18 | return state; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/) 19 | ); 20 | 21 | type Config = { 22 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 23 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 24 | }; 25 | 26 | export function register(config?: Config) { 27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 28 | // The URL constructor is available in all browsers that support SW. 29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 30 | if (publicUrl.origin !== window.location.origin) { 31 | // Our service worker won't work if PUBLIC_URL is on a different origin 32 | // from what our page is served on. This might happen if a CDN is used to 33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 34 | return; 35 | } 36 | 37 | window.addEventListener('load', () => { 38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 39 | 40 | if (isLocalhost) { 41 | // This is running on localhost. Let's check if a service worker still exists or not. 42 | checkValidServiceWorker(swUrl, config); 43 | 44 | // Add some additional logging to localhost, pointing developers to the 45 | // service worker/PWA documentation. 46 | navigator.serviceWorker.ready.then(() => { 47 | console.log( 48 | 'This web app is being served cache-first by a service ' + 49 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 50 | ); 51 | }); 52 | } else { 53 | // Is not localhost. Just register service worker 54 | registerValidSW(swUrl, config); 55 | } 56 | }); 57 | } 58 | } 59 | 60 | function registerValidSW(swUrl: string, config?: Config) { 61 | navigator.serviceWorker 62 | .register(swUrl) 63 | .then(registration => { 64 | registration.onupdatefound = () => { 65 | const installingWorker = registration.installing; 66 | if (installingWorker == null) { 67 | return; 68 | } 69 | installingWorker.onstatechange = () => { 70 | if (installingWorker.state === 'installed') { 71 | if (navigator.serviceWorker.controller) { 72 | // At this point, the updated precached content has been fetched, 73 | // but the previous service worker will still serve the older 74 | // content until all client tabs are closed. 75 | console.log( 76 | 'New content is available and will be used when all ' + 77 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 78 | ); 79 | 80 | // Execute callback 81 | if (config && config.onUpdate) { 82 | config.onUpdate(registration); 83 | } 84 | } else { 85 | // At this point, everything has been precached. 86 | // It's the perfect time to display a 87 | // "Content is cached for offline use." message. 88 | console.log('Content is cached for offline use.'); 89 | 90 | // Execute callback 91 | if (config && config.onSuccess) { 92 | config.onSuccess(registration); 93 | } 94 | } 95 | } 96 | }; 97 | }; 98 | }) 99 | .catch(error => { 100 | console.error('Error during service worker registration:', error); 101 | }); 102 | } 103 | 104 | function checkValidServiceWorker(swUrl: string, config?: Config) { 105 | // Check if the service worker can be found. If it can't reload the page. 106 | fetch(swUrl, { 107 | headers: {'Service-Worker': 'script'}, 108 | }) 109 | .then(response => { 110 | // Ensure service worker exists, and that we really are getting a JS file. 111 | const contentType = response.headers.get('content-type'); 112 | if ( 113 | response.status === 404 || 114 | (contentType != null && contentType.indexOf('javascript') === -1) 115 | ) { 116 | // No service worker found. Probably a different app. Reload the page. 117 | navigator.serviceWorker.ready.then(registration => { 118 | registration.unregister().then(() => { 119 | window.location.reload(); 120 | }); 121 | }); 122 | } else { 123 | // Service worker found. Proceed as normal. 124 | registerValidSW(swUrl, config); 125 | } 126 | }) 127 | .catch(() => { 128 | console.log('No internet connection found. App is running in offline mode.'); 129 | }); 130 | } 131 | 132 | export function unregister() { 133 | if ('serviceWorker' in navigator) { 134 | navigator.serviceWorker.ready 135 | .then(registration => { 136 | registration.unregister(); 137 | }) 138 | .catch(error => { 139 | console.error(error.message); 140 | }); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /frontend/src/services/addLiquidity.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as constants from './constants'; 3 | import algosdk from 'algosdk'; 4 | 5 | export default async function addLiquidity( 6 | from: string, 7 | escrowAddr: string, 8 | token1Amount: number, 9 | token1Index: number, 10 | token2Amount: number, 11 | token2Index: number, 12 | minLiquidityTokenReceived: number 13 | ) { 14 | try { 15 | const encodedAppArgs = [ 16 | Buffer.from('a').toString('base64'), 17 | Buffer.from(minLiquidityTokenReceived).toString('base64'), 18 | ]; 19 | 20 | const txParams = await AlgoSigner.algod({ 21 | ledger: constants.LEDGER_NAME, 22 | path: '/v2/transactions/params', 23 | }); 24 | 25 | // Call to validator 26 | let txn1 = { 27 | type: 'appl', 28 | from: from, 29 | appIndex: constants.VALIDATOR_APP_ID, 30 | appOnComplete: 0, // 0 == NoOp 31 | appArgs: encodedAppArgs, 32 | appAccounts: [escrowAddr], 33 | fee: txParams['fee'], 34 | firstRound: txParams['last-round'], 35 | lastRound: txParams['last-round'] + 1000, 36 | genesisID: txParams['genesis-id'], 37 | genesisHash: txParams['genesis-hash'], 38 | }; 39 | 40 | // Transaction to manager 41 | let txn2 = { 42 | type: 'appl', 43 | from: from, 44 | appIndex: constants.MANAGER_APP_ID, 45 | appOnComplete: 0, // 0 == NoOp 46 | appArgs: encodedAppArgs, 47 | appAccounts: [escrowAddr], 48 | fee: txParams['fee'], 49 | firstRound: txParams['last-round'], 50 | lastRound: txParams['last-round'] + 1000, 51 | genesisID: txParams['genesis-id'], 52 | genesisHash: txParams['genesis-hash'], 53 | }; 54 | 55 | // Send Token1 to Escrow 56 | let txn3 = { 57 | type: 'axfer', 58 | from: from, 59 | to: escrowAddr, 60 | amount: token1Amount, 61 | assetIndex: token1Index, 62 | fee: txParams['fee'], 63 | firstRound: txParams['last-round'], 64 | lastRound: txParams['last-round'] + 1000, 65 | genesisID: txParams['genesis-id'], 66 | genesisHash: txParams['genesis-hash'], 67 | }; 68 | 69 | // Send Token2 to Escrow 70 | let txn4 = { 71 | type: 'axfer', 72 | from: from, 73 | to: escrowAddr, 74 | amount: token2Amount, 75 | assetIndex: token2Index, 76 | fee: txParams['fee'], 77 | firstRound: txParams['last-round'], 78 | lastRound: txParams['last-round'] + 1000, 79 | genesisID: txParams['genesis-id'], 80 | genesisHash: txParams['genesis-hash'], 81 | }; 82 | 83 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3, txn4]); 84 | 85 | // Modify the group fields in original transactions to be base64 encoded strings 86 | txn1.group = txnGroup[0].group.toString('base64'); 87 | txn2.group = txnGroup[1].group.toString('base64'); 88 | txn3.group = txnGroup[2].group.toString('base64'); 89 | txn4.group = txnGroup[3].group.toString('base64'); 90 | 91 | let signedTxn1 = await AlgoSigner.sign(txn1); 92 | let signedTxn2 = await AlgoSigner.sign(txn2); 93 | let signedTxn3 = await AlgoSigner.sign(txn3); 94 | let signedTxn4 = await AlgoSigner.sign(txn4); 95 | 96 | if (!(signedTxn1 && signedTxn2 && signedTxn3 && signedTxn4)) { 97 | return console.error('User rejected signatures'); 98 | } 99 | 100 | // Get the decoded binary Uint8Array values from the blobs 101 | const decoded_1 = new Uint8Array( 102 | atob(signedTxn1.blob) 103 | .split('') 104 | .map(x => x.charCodeAt(0)) 105 | ); 106 | const decoded_2 = new Uint8Array( 107 | atob(signedTxn2.blob) 108 | .split('') 109 | .map(x => x.charCodeAt(0)) 110 | ); 111 | const decoded_3 = new Uint8Array( 112 | atob(signedTxn3.blob) 113 | .split('') 114 | .map(x => x.charCodeAt(0)) 115 | ); 116 | 117 | // Use their combined length to create a 3rd array 118 | let combined_decoded_txns = new Uint8Array( 119 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 120 | ); 121 | 122 | // Starting at the 0 position, fill in the binary for the first object 123 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 124 | // Starting at the first object byte length, fill in the 2nd binary value 125 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 126 | // Starting at the first+second object byte length, fill in the 3rd binary value 127 | combined_decoded_txns.set( 128 | new Uint8Array(decoded_3), 129 | decoded_1.byteLength + decoded_2.byteLength 130 | ); 131 | 132 | // Modify our combined array values back to an encoded 64bit string 133 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 134 | 135 | const res = await AlgoSigner.send({ 136 | ledger: constants.LEDGER_NAME, 137 | tx: grouped_txns, 138 | }); 139 | } catch (e) { 140 | console.error(e); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /frontend/src/services/constants.ts: -------------------------------------------------------------------------------- 1 | export const LEDGER_NAME = 'TestNet'; 2 | export const MANAGER_APP_ID = 14973861; 3 | export const VALIDATOR_APP_ID = 14973862; 4 | -------------------------------------------------------------------------------- /frontend/src/services/helpers.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as constants from './constants'; 3 | import PairDetails from '../models/PairDetails'; 4 | import algosdk from 'algosdk'; 5 | 6 | export async function calculateUnused( 7 | user: string, 8 | escrow: string, 9 | prefix: string 10 | ): Promise { 11 | const accountInfo = await AlgoSigner.algod({ 12 | ledger: constants.LEDGER_NAME, 13 | path: `/v2/accounts/${user}`, 14 | }); 15 | const localState = accountInfo['apps-local-state']; 16 | 17 | for (let block of localState) { 18 | if (block.id === constants.MANAGER_APP_ID) { 19 | for (let kvs of block['key-value']) { 20 | let decodedKey = Buffer.from(kvs.key, 'base64'); 21 | let prefixKey = decodedKey.slice(0, 2).toString('utf-8'); 22 | let addrBytes = decodedKey.slice(2); 23 | let resultantEscrowAddress = algosdk.encodeAddress(addrBytes); 24 | 25 | if (prefixKey === prefix && resultantEscrowAddress === escrow) { 26 | console.log(prefixKey); 27 | console.log(resultantEscrowAddress); 28 | console.log(kvs.value.uint); 29 | return parseInt(kvs.value.uint); 30 | } 31 | } 32 | } 33 | } 34 | 35 | return 0; 36 | } 37 | 38 | export async function getToken1Refund(user: string, details: PairDetails) { 39 | const encodedAppArgs = [Buffer.from('r').toString('base64')]; 40 | 41 | const unusedToken1 = await calculateUnused(user, details.escrowAddress, 'U1'); 42 | if (unusedToken1 !== 0) { 43 | try { 44 | const txParams = await AlgoSigner.algod({ 45 | ledger: constants.LEDGER_NAME, 46 | path: '/v2/transactions/params', 47 | }); 48 | 49 | // Call to validator 50 | let txn1 = { 51 | type: 'appl', 52 | from: user, 53 | appIndex: constants.VALIDATOR_APP_ID, 54 | appOnComplete: 0, // 0 == NoOp 55 | appArgs: encodedAppArgs, 56 | appAccounts: [details.escrowAddress], 57 | fee: txParams['fee'], 58 | firstRound: txParams['last-round'], 59 | lastRound: txParams['last-round'] + 1000, 60 | genesisID: txParams['genesis-id'], 61 | genesisHash: txParams['genesis-hash'], 62 | }; 63 | 64 | // Call to manager 65 | let txn2 = { 66 | type: 'appl', 67 | from: user, 68 | appIndex: constants.MANAGER_APP_ID, 69 | appOnComplete: 0, // 0 == NoOp 70 | appArgs: encodedAppArgs, 71 | appAccounts: [details.escrowAddress], 72 | fee: txParams['fee'], 73 | firstRound: txParams['last-round'], 74 | lastRound: txParams['last-round'] + 1000, 75 | genesisID: txParams['genesis-id'], 76 | genesisHash: txParams['genesis-hash'], 77 | }; 78 | 79 | // Make logicsig 80 | let program = Uint8Array.from(Buffer.from(details.escrowLogicSig, 'base64')); 81 | let lsig = algosdk.makeLogicSig(program); 82 | 83 | let txn3 = { 84 | type: 'axfer', 85 | from: details.escrowAddress, 86 | to: user, 87 | amount: unusedToken1, 88 | assetIndex: details.token1Index, 89 | fee: txParams['fee'], 90 | firstRound: txParams['last-round'], 91 | lastRound: txParams['last-round'] + 1000, 92 | genesisID: txParams['genesis-id'], 93 | genesisHash: txParams['genesis-hash'], 94 | }; 95 | 96 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 97 | 98 | // Modify the group fields in original transactions to be base64 encoded strings 99 | txn1.group = txnGroup[0].group.toString('base64'); 100 | txn2.group = txnGroup[1].group.toString('base64'); 101 | txn3.group = txnGroup[2].group.toString('base64'); 102 | 103 | let signedTxn1 = await AlgoSigner.sign(txn1); 104 | let signedTxn2 = await AlgoSigner.sign(txn2); 105 | let signedTxn3 = algosdk.signLogicSigTransaction(txn3, lsig); 106 | 107 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 108 | return console.error('User rejected signatures'); 109 | } 110 | 111 | // Get the decoded binary UInt8Array values from the blobs 112 | const decoded_1 = new Uint8Array( 113 | atob(signedTxn1.blob) 114 | .split('') 115 | .map(x => x.charCodeAt(0)) 116 | ); 117 | 118 | const decoded_2 = new Uint8Array( 119 | atob(signedTxn2.blob) 120 | .split('') 121 | .map(x => x.charCodeAt(0)) 122 | ); 123 | 124 | const decoded_3 = new Uint8Array( 125 | atob(signedTxn3.blob) 126 | .split('') 127 | .map(x => x.charCodeAt(0)) 128 | ); 129 | 130 | // Use their combined length to create a 3rd array 131 | let combined_decoded_txns = new Uint8Array( 132 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 133 | ); 134 | 135 | // Starting at the 0 position, fill in the binary for the first object 136 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 137 | // Starting at the first object byte length, fill in the 2nd binary value 138 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 139 | // Starting at the first+second object byte length, fill in the 3rd binary value 140 | combined_decoded_txns.set( 141 | new Uint8Array(decoded_3), 142 | decoded_1.byteLength + decoded_2.byteLength 143 | ); 144 | 145 | // Modify our combined array values back to an encoded 64bit string 146 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 147 | 148 | await AlgoSigner.send({ 149 | ledger: constants.LEDGER_NAME, 150 | tx: grouped_txns, 151 | }); 152 | } catch (e) { 153 | console.error(e); 154 | } 155 | } else { 156 | console.log('No unused token 1'); 157 | } 158 | } 159 | 160 | export async function getToken2Refund(user: string, details: PairDetails) { 161 | const encodedAppArgs = [Buffer.from('r').toString('base64')]; 162 | 163 | const unusedToken2 = await calculateUnused(user, details.escrowAddress, 'U2'); 164 | 165 | if (unusedToken2 !== 0) { 166 | try { 167 | const txParams = await AlgoSigner.algod({ 168 | ledger: constants.LEDGER_NAME, 169 | path: '/v2/transactions/params', 170 | }); 171 | 172 | // Call to validator 173 | let txn1 = { 174 | type: 'appl', 175 | from: user, 176 | appIndex: constants.VALIDATOR_APP_ID, 177 | appOnComplete: 0, // 0 == NoOp 178 | appArgs: encodedAppArgs, 179 | appAccounts: [details.escrowAddress], 180 | fee: txParams['fee'], 181 | firstRound: txParams['last-round'], 182 | lastRound: txParams['last-round'] + 1000, 183 | genesisID: txParams['genesis-id'], 184 | genesisHash: txParams['genesis-hash'], 185 | }; 186 | 187 | // Call to manager 188 | let txn2 = { 189 | type: 'appl', 190 | from: user, 191 | appIndex: constants.MANAGER_APP_ID, 192 | appOnComplete: 0, // 0 == NoOp 193 | appArgs: encodedAppArgs, 194 | appAccounts: [details.escrowAddress], 195 | fee: txParams['fee'], 196 | firstRound: txParams['last-round'], 197 | lastRound: txParams['last-round'] + 1000, 198 | genesisID: txParams['genesis-id'], 199 | genesisHash: txParams['genesis-hash'], 200 | }; 201 | 202 | // Make logicsig 203 | let program = Uint8Array.from(Buffer.from(details.escrowLogicSig, 'base64')); 204 | let lsig = algosdk.makeLogicSig(program); 205 | 206 | let txn3 = { 207 | type: 'axfer', 208 | from: details.escrowAddress, 209 | to: user, 210 | amount: unusedToken2, 211 | assetIndex: details.token2Index, 212 | fee: txParams['fee'], 213 | firstRound: txParams['last-round'], 214 | lastRound: txParams['last-round'] + 1000, 215 | genesisID: txParams['genesis-id'], 216 | genesisHash: txParams['genesis-hash'], 217 | }; 218 | 219 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 220 | 221 | // Modify the group fields in original transactions to be base64 encoded strings 222 | txn1.group = txnGroup[0].group.toString('base64'); 223 | txn2.group = txnGroup[1].group.toString('base64'); 224 | txn3.group = txnGroup[2].group.toString('base64'); 225 | 226 | let signedTxn1 = await AlgoSigner.sign(txn1); 227 | let signedTxn2 = await AlgoSigner.sign(txn2); 228 | let signedTxn3 = algosdk.signLogicSigTransaction(txn3, lsig); 229 | 230 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 231 | return console.error('User rejected signatures'); 232 | } 233 | 234 | // Get the decoded binary UInt8Array values from the blobs 235 | const decoded_1 = new Uint8Array( 236 | atob(signedTxn1.blob) 237 | .split('') 238 | .map(x => x.charCodeAt(0)) 239 | ); 240 | 241 | const decoded_2 = new Uint8Array( 242 | atob(signedTxn2.blob) 243 | .split('') 244 | .map(x => x.charCodeAt(0)) 245 | ); 246 | 247 | const decoded_3 = new Uint8Array( 248 | atob(signedTxn3.blob) 249 | .split('') 250 | .map(x => x.charCodeAt(0)) 251 | ); 252 | 253 | // Use their combined length to create a 3rd array 254 | let combined_decoded_txns = new Uint8Array( 255 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 256 | ); 257 | 258 | // Starting at the 0 position, fill in the binary for the first object 259 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 260 | // Starting at the first object byte length, fill in the 2nd binary value 261 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 262 | // Starting at the first+second object byte length, fill in the 3rd binary value 263 | combined_decoded_txns.set( 264 | new Uint8Array(decoded_3), 265 | decoded_1.byteLength + decoded_2.byteLength 266 | ); 267 | 268 | // Modify our combined array values back to an encoded 64bit string 269 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 270 | 271 | await AlgoSigner.send({ 272 | ledger: constants.LEDGER_NAME, 273 | tx: grouped_txns, 274 | }); 275 | } catch (e) { 276 | console.error(e); 277 | } 278 | } else { 279 | console.log('No unused token 2'); 280 | } 281 | } 282 | 283 | export async function getLiquidityTokenRefund(user: string, details: PairDetails) { 284 | const encodedAppArgs = [Buffer.from('r').toString('base64')]; 285 | 286 | const unusedLiquidityToken = await calculateUnused(user, details.escrowAddress, 'UL'); 287 | 288 | if (unusedLiquidityToken !== 0) { 289 | try { 290 | const txParams = await AlgoSigner.algod({ 291 | ledger: constants.LEDGER_NAME, 292 | path: '/v2/transactions/params', 293 | }); 294 | 295 | // Call to validator 296 | let txn1 = { 297 | type: 'appl', 298 | from: user, 299 | appIndex: constants.VALIDATOR_APP_ID, 300 | appOnComplete: 0, // 0 == NoOp 301 | appArgs: encodedAppArgs, 302 | appAccounts: [details.escrowAddress], 303 | fee: txParams['fee'], 304 | firstRound: txParams['last-round'], 305 | lastRound: txParams['last-round'] + 1000, 306 | genesisID: txParams['genesis-id'], 307 | genesisHash: txParams['genesis-hash'], 308 | }; 309 | 310 | // Call to manager 311 | let txn2 = { 312 | type: 'appl', 313 | from: user, 314 | appIndex: constants.MANAGER_APP_ID, 315 | appOnComplete: 0, // 0 == NoOp 316 | appArgs: encodedAppArgs, 317 | appAccounts: [details.escrowAddress], 318 | fee: txParams['fee'], 319 | firstRound: txParams['last-round'], 320 | lastRound: txParams['last-round'] + 1000, 321 | genesisID: txParams['genesis-id'], 322 | genesisHash: txParams['genesis-hash'], 323 | }; 324 | 325 | // Make logicsig 326 | let program = Uint8Array.from(Buffer.from(details.escrowLogicSig, 'base64')); 327 | let lsig = algosdk.makeLogicSig(program); 328 | 329 | let txn3 = { 330 | type: 'axfer', 331 | from: details.escrowAddress, 332 | to: user, 333 | amount: unusedLiquidityToken, 334 | assetIndex: details.liquidityTokenIndex, 335 | fee: txParams['fee'], 336 | firstRound: txParams['last-round'], 337 | lastRound: txParams['last-round'] + 1000, 338 | genesisID: txParams['genesis-id'], 339 | genesisHash: txParams['genesis-hash'], 340 | }; 341 | 342 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 343 | 344 | // Modify the group fields in original transactions to be base64 encoded strings 345 | txn1.group = txnGroup[0].group.toString('base64'); 346 | txn2.group = txnGroup[1].group.toString('base64'); 347 | txn3.group = txnGroup[2].group.toString('base64'); 348 | 349 | let signedTxn1 = await AlgoSigner.sign(txn1); 350 | let signedTxn2 = await AlgoSigner.sign(txn2); 351 | let signedTxn3 = algosdk.signLogicSigTransaction(txn3, lsig); 352 | 353 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 354 | return console.error('User rejected signatures'); 355 | } 356 | 357 | // Get the decoded binary UInt8Array values from the blobs 358 | const decoded_1 = new Uint8Array( 359 | atob(signedTxn1.blob) 360 | .split('') 361 | .map(x => x.charCodeAt(0)) 362 | ); 363 | 364 | const decoded_2 = new Uint8Array( 365 | atob(signedTxn2.blob) 366 | .split('') 367 | .map(x => x.charCodeAt(0)) 368 | ); 369 | 370 | const decoded_3 = new Uint8Array( 371 | atob(signedTxn3.blob) 372 | .split('') 373 | .map(x => x.charCodeAt(0)) 374 | ); 375 | 376 | // Use their combined length to create a 3rd array 377 | let combined_decoded_txns = new Uint8Array( 378 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 379 | ); 380 | 381 | // Starting at the 0 position, fill in the binary for the first object 382 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 383 | // Starting at the first object byte length, fill in the 2nd binary value 384 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 385 | // Starting at the first+second object byte length, fill in the 3rd binary value 386 | combined_decoded_txns.set( 387 | new Uint8Array(decoded_3), 388 | decoded_1.byteLength + decoded_2.byteLength 389 | ); 390 | 391 | // Modify our combined array values back to an encoded 64bit string 392 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 393 | 394 | await AlgoSigner.send({ 395 | ledger: constants.LEDGER_NAME, 396 | tx: grouped_txns, 397 | }); 398 | } catch (e) { 399 | console.error(e); 400 | } 401 | } else { 402 | console.log('No unused liquidity token'); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /frontend/src/services/swapToken1ForToken2.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as constants from './constants'; 3 | import algosdk from 'algosdk'; 4 | 5 | export default async function swapToken1ForToken2( 6 | from: string, 7 | escrowAddr: string, 8 | token1Amount: number, 9 | token1Index: number, 10 | minToken2Received: number 11 | ) { 12 | try { 13 | const encodedAppArgs = [ 14 | Buffer.from('s1').toString('base64'), 15 | Buffer.from(minToken2Received.toString()).toString('base64'), 16 | ]; 17 | 18 | const txParams = await AlgoSigner.algod({ 19 | ledger: constants.LEDGER_NAME, 20 | path: '/v2/transactions/params', 21 | }); 22 | 23 | console.log(txParams); 24 | 25 | 26 | // Call to validator 27 | let txn1 = { 28 | type: 'appl', 29 | from: from, 30 | appIndex: constants.VALIDATOR_APP_ID, 31 | appOnComplete: 0, // 0 == NoOp 32 | appArgs: encodedAppArgs, 33 | // appAccounts: [escrowAddr], 34 | fee: txParams['min-fee'], 35 | firstRound: txParams['last-round'], 36 | lastRound: txParams['last-round'] + 1000, 37 | genesisID: txParams['genesis-id'], 38 | genesisHash: txParams['genesis-hash'], 39 | }; 40 | 41 | // Call to manager 42 | let txn2 = { 43 | type: 'appl', 44 | from: from, 45 | appIndex: constants.MANAGER_APP_ID, 46 | appOnComplete: 0, // 0 == NoOp 47 | appArgs: encodedAppArgs, 48 | // appAccounts: [escrowAddr], 49 | fee: txParams['min-fee'], 50 | firstRound: txParams['last-round'], 51 | lastRound: txParams['last-round'] + 1000, 52 | genesisID: txParams['genesis-id'], 53 | genesisHash: txParams['genesis-hash'], 54 | }; 55 | 56 | // Send Token1 to Escrow 57 | let txn3 = { 58 | type: 'axfer', 59 | from: from, 60 | to: escrowAddr, 61 | amount: token1Amount, 62 | assetIndex: token1Index, 63 | fee: txParams['min-fee'], 64 | firstRound: txParams['last-round'], 65 | lastRound: txParams['last-round'] + 1000, 66 | genesisID: txParams['genesis-id'], 67 | genesisHash: txParams['genesis-hash'], 68 | }; 69 | 70 | console.log([txn1, txn2, txn3]) 71 | 72 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 73 | 74 | console.log(txnGroup); 75 | 76 | // Modify the group fields in orginal transactions to be base64 encoded strings 77 | txn1.group = txnGroup[0].group.toString('base64'); 78 | txn2.group = txnGroup[1].group.toString('base64'); 79 | txn3.group = txnGroup[2].group.toString('base64'); 80 | 81 | let signedTxn1 = await AlgoSigner.sign(txn1); 82 | let signedTxn2 = await AlgoSigner.sign(txn2); 83 | let signedTxn3 = await AlgoSigner.sign(txn3); 84 | 85 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 86 | return console.error('User rejected signatures'); 87 | } 88 | 89 | // Get the decoded binary Uint8Array values from the blobs 90 | const decoded_1 = new Uint8Array( 91 | atob(signed1.blob) 92 | .split('') 93 | .map(x => x.charCodeAt(0)) 94 | ); 95 | const decoded_2 = new Uint8Array( 96 | atob(signed2.blob) 97 | .split('') 98 | .map(x => x.charCodeAt(0)) 99 | ); 100 | const decoded_3 = new Uint8Array( 101 | atob(signed3.blob) 102 | .split('') 103 | .map(x => x.charCodeAt(0)) 104 | ); 105 | 106 | // Use their combined length to create a 3rd array 107 | let combined_decoded_txns = new Uint8Array( 108 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 109 | ); 110 | 111 | // Starting at the 0 position, fill in the binary for the first object 112 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 113 | // Starting at the first object byte length, fill in the 2nd binary value 114 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 115 | // Starting at the first+second object byte length, fill in the 3rd binary value 116 | combined_decoded_txns.set( 117 | new Uint8Array(decoded_3), 118 | decoded_1.byteLength + decoded_2.byteLength 119 | ); 120 | 121 | // Modify our combined array values back to an encoded 64bit string 122 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 123 | 124 | const res = await AlgoSigner.send({ 125 | ledger: constants.LEDGER_NAME, 126 | tx: grouped_txns, 127 | }); 128 | console.log(res); 129 | } catch (e) { 130 | console.error(e); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /frontend/src/services/swapToken2ForToken1.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as constants from './constants'; 3 | import algosdk from 'algosdk'; 4 | 5 | export default async function swapToken2ForToken1( 6 | from: string, 7 | escrowAddr: string, 8 | token2Amount: number, 9 | token2Index: number, 10 | minToken1Received: number 11 | ) { 12 | try { 13 | // TODO: encode these and send with txns 14 | const args = [ 15 | Buffer.from('s2').toString('base64'), 16 | Buffer.from(minToken1Received).toString('base64'), 17 | ]; 18 | 19 | const txParams = await AlgoSigner.algod({ 20 | ledger: constants.LEDGER_NAME, 21 | path: '/v2/transactions/params', 22 | }); 23 | 24 | // Call to validator 25 | let txn1 = { 26 | type: 'appl', 27 | from: from, 28 | appIndex: constants.VALIDATOR_APP_ID, 29 | appOnComplete: 0, // 0 == NoOp 30 | appArgs: encodedAppArgs, 31 | appAccounts: [escrowAddr], 32 | fee: txParams['fee'], 33 | firstRound: txParams['last-round'], 34 | lastRound: txParams['last-round'] + 1000, 35 | genesisID: txParams['genesis-id'], 36 | genesisHash: txParams['genesis-hash'], 37 | }; 38 | 39 | // Call to manager 40 | let txn2 = { 41 | type: 'appl', 42 | from: from, 43 | appIndex: constants.MANAGER_APP_ID, 44 | appOnComplete: 0, // 0 == NoOp 45 | appArgs: encodedAppArgs, 46 | appAccounts: [escrowAddr], 47 | fee: txParams['fee'], 48 | firstRound: txParams['last-round'], 49 | lastRound: txParams['last-round'] + 1000, 50 | genesisID: txParams['genesis-id'], 51 | genesisHash: txParams['genesis-hash'], 52 | }; 53 | 54 | // Send Token2 to Escrow 55 | let txn3 = { 56 | type: 'axfer', 57 | from: from, 58 | to: escrowAddr, 59 | amount: token2Amount, 60 | assetIndex: token2Index, 61 | fee: txParams['fee'], 62 | firstRound: txParams['last-round'], 63 | lastRound: txParams['last-round'] + 1000, 64 | genesisID: txParams['genesis-id'], 65 | genesisHash: txParams['genesis-hash'], 66 | }; 67 | 68 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 69 | 70 | // Modify the group fields in orginal transactions to be base64 encoded strings 71 | txn1.group = txnGroup[0].group.toString('base64'); 72 | txn2.group = txnGroup[1].group.toString('base64'); 73 | txn3.group = txnGroup[2].group.toString('base64'); 74 | 75 | let signedTxn1 = await AlgoSigner.sign(txn1); 76 | let signedTxn2 = await AlgoSigner.sign(txn2); 77 | let signedTxn3 = await AlgoSigner.sign(txn3); 78 | 79 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 80 | return console.error('User rejected signatures'); 81 | } 82 | 83 | // Get the decoded binary Uint8Array values from the blobs 84 | const decoded_1 = new Uint8Array( 85 | atob(signed1.blob) 86 | .split('') 87 | .map(x => x.charCodeAt(0)) 88 | ); 89 | const decoded_2 = new Uint8Array( 90 | atob(signed2.blob) 91 | .split('') 92 | .map(x => x.charCodeAt(0)) 93 | ); 94 | const decoded_3 = new Uint8Array( 95 | atob(signed3.blob) 96 | .split('') 97 | .map(x => x.charCodeAt(0)) 98 | ); 99 | 100 | // Use their combined length to create a 3rd array 101 | let combined_decoded_txns = new Uint8Array( 102 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 103 | ); 104 | 105 | // Starting at the 0 position, fill in the binary for the first object 106 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 107 | // Starting at the first object byte length, fill in the 2nd binary value 108 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 109 | // Starting at the first+second object byte length, fill in the 3rd binary value 110 | combined_decoded_txns.set( 111 | new Uint8Array(decoded_3), 112 | decoded_1.byteLength + decoded_2.byteLength 113 | ); 114 | 115 | // Modify our combined array values back to an encoded 64bit string 116 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 117 | 118 | const res = await AlgoSigner.send({ 119 | ledger: constants.LEDGER_NAME, 120 | tx: grouped_txns, 121 | }); 122 | console.log(res); 123 | } catch (e) { 124 | console.error(e); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /frontend/src/services/withdrawLiquidity.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import * as constants from './constants'; 3 | import algosdk from 'algosdk'; 4 | 5 | export default async function withdrawLiquidity( 6 | from: string, 7 | escrowAddr: string, 8 | liquidityTokenAmount: number, 9 | liquidityTokenIndex: number, 10 | minToken1Received: number, 11 | minToken2Received: number 12 | ) { 13 | try { 14 | const encodedAppArgs = [ 15 | Buffer.from('w').toString('base64'), 16 | Buffer.from(minToken1Received).toString('base64'), 17 | Buffer.from(minToken2Received).toString('base64'), 18 | ]; 19 | 20 | const txParams = await AlgoSigner.algod({ 21 | ledger: constants.LEDGER_NAME, 22 | path: '/v2/transactions/params', 23 | }); 24 | 25 | // Call to validator 26 | let txn1 = { 27 | type: 'appl', 28 | from: from, 29 | appIndex: constants.VALIDATOR_APP_ID, 30 | appOnComplete: 0, // 0 == NoOp 31 | appArgs: encodedAppArgs, 32 | appAccounts: [escrowAddr], 33 | fee: txParams['fee'], 34 | firstRound: txParams['last-round'], 35 | lastRound: txParams['last-round'] + 1000, 36 | genesisID: txParams['genesis-id'], 37 | genesisHash: txParams['genesis-hash'], 38 | }; 39 | 40 | // Call to manager 41 | let txn2 = { 42 | type: 'appl', 43 | from: from, 44 | appIndex: constants.MANAGER_APP_ID, 45 | appOnComplete: 0, // 0 == NoOp 46 | appArgs: encodedAppArgs, 47 | appAccounts: [escrowAddr], 48 | fee: txParams['fee'], 49 | firstRound: txParams['last-round'], 50 | lastRound: txParams['last-round'] + 1000, 51 | genesisID: txParams['genesis-id'], 52 | genesisHash: txParams['genesis-hash'], 53 | }; 54 | 55 | // Send liquidity token to Escrow 56 | let txn3 = { 57 | type: 'axfer', 58 | from: from, 59 | to: escrowAddr, 60 | amount: liquidityTokenAmount, 61 | assetIndex: liquidityTokenIndex, 62 | fee: txParams['fee'], 63 | firstRound: txParams['last-round'], 64 | lastRound: txParams['last-round'] + 1000, 65 | genesisID: txParams['genesis-id'], 66 | genesisHash: txParams['genesis-hash'], 67 | }; 68 | 69 | let txnGroup = await algosdk.assignGroupID([txn1, txn2, txn3]); 70 | 71 | // Modify the group fields in orginal transactions to be base64 encoded strings 72 | txn1.group = txnGroup[0].group.toString('base64'); 73 | txn2.group = txnGroup[1].group.toString('base64'); 74 | txn3.group = txnGroup[2].group.toString('base64'); 75 | 76 | let signedTxn1 = await AlgoSigner.sign(txn1); 77 | let signedTxn2 = await AlgoSigner.sign(txn2); 78 | let signedTxn3 = await AlgoSigner.sign(txn3); 79 | 80 | if (!(signedTxn1 && signedTxn2 && signedTxn3)) { 81 | return console.error('User rejected signatures'); 82 | } 83 | 84 | // Get the decoded binary Uint8Array values from the blobs 85 | const decoded_1 = new Uint8Array( 86 | atob(signed1.blob) 87 | .split('') 88 | .map(x => x.charCodeAt(0)) 89 | ); 90 | const decoded_2 = new Uint8Array( 91 | atob(signed2.blob) 92 | .split('') 93 | .map(x => x.charCodeAt(0)) 94 | ); 95 | const decoded_3 = new Uint8Array( 96 | atob(signed3.blob) 97 | .split('') 98 | .map(x => x.charCodeAt(0)) 99 | ); 100 | 101 | // Use their combined length to create a 3rd array 102 | let combined_decoded_txns = new Uint8Array( 103 | decoded_1.byteLength + decoded_2.byteLength + decoded_3.byteLength 104 | ); 105 | 106 | // Starting at the 0 position, fill in the binary for the first object 107 | combined_decoded_txns.set(new Uint8Array(decoded_1), 0); 108 | // Starting at the first object byte length, fill in the 2nd binary value 109 | combined_decoded_txns.set(new Uint8Array(decoded_2), decoded_1.byteLength); 110 | // Starting at the first+second object byte length, fill in the 3rd binary value 111 | combined_decoded_txns.set( 112 | new Uint8Array(decoded_3), 113 | decoded_1.byteLength + decoded_2.byteLength 114 | ); 115 | 116 | // Modify our combined array values back to an encoded 64bit string 117 | const grouped_txns = btoa(String.fromCharCode.apply(null, combined_decoded_txns)); 118 | 119 | const res = await AlgoSigner.send({ 120 | ledger: constants.LEDGER_NAME, 121 | tx: grouped_txns, 122 | }); 123 | console.log(res); 124 | } catch (e) { 125 | console.error(e); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src"] 19 | } 20 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/algorand/pyteal@e4887d9 2 | py-algorand-sdk==1.4.0 -------------------------------------------------------------------------------- /scripts/dump_add_liquidity_txns.sh: -------------------------------------------------------------------------------- 1 | export ALGORAND_DATA=~/node/data 2 | 3 | export ESCROW_ADDRESS="GE25LQU674ZQFAYJM2RUCRXQRPWIMAEOUI26NHRDW77GEHRANYSRUTQYFE" 4 | export ESCROW_LOGICSIG="AiALAQMEBqqAygangMoGAozr3AWrgMoGrIDKBq2AygYyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=" 5 | 6 | export MANAGER_INDEX="13795367" 7 | export VALIDATOR_INDEX="13795370" 8 | export TOKEN1_INDEX="13795371" 9 | export TOKEN2_INDEX="13795372" 10 | export LIQUIDITY_TOKEN_INDEX="13795373" 11 | export TEST_ACCOUNT_ADDRESS="LZVKOKRULFYC7TIAOMA76VVPYWUJPRZ6V6FE7ZTMYAL7KNZNKR75HNA3HY" 12 | 13 | ./goal app call --app-id ${VALIDATOR_INDEX} --app-arg "str:a" --app-arg "str:1" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn1.tx 14 | 15 | ./goal app call --app-id ${MANAGER_INDEX} --app-arg "str:a" --app-arg "str:1" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn2.tx 16 | 17 | ./goal asset send -a 500000000 --assetid ${TOKEN1_INDEX} -f ${TEST_ACCOUNT_ADDRESS} -t ${ESCROW_ADDRESS} --out=txn3.tx 18 | 19 | ./goal asset send -a 500000000 --assetid ${TOKEN2_INDEX} -f ${TEST_ACCOUNT_ADDRESS} -t ${ESCROW_ADDRESS} --out=txn4.tx 20 | 21 | cat txn1.tx txn2.tx txn3.tx txn4.tx > combinedtxns.tx 22 | 23 | ./goal clerk group -i combinedtxns.tx -o groupedtxns.tx 24 | ./goal clerk split -i groupedtxns.tx -o split.tx 25 | 26 | ./goal clerk sign -i split-0.tx -o signout-0.tx 27 | ./goal clerk sign -i split-1.tx -o signout-1.tx 28 | ./goal clerk sign -i split-2.tx -o signout-2.tx 29 | ./goal clerk sign -i split-3.tx -o signout-3.tx 30 | 31 | cat signout-0.tx signout-1.tx signout-2.tx signout-3.tx > signout.tx 32 | 33 | ./goal clerk dryrun -t signout.tx --dryrun-dump --dryrun-dump-format json -o add-liq-dr.json 34 | -------------------------------------------------------------------------------- /scripts/dump_swap_t1_for_t2_txns.sh: -------------------------------------------------------------------------------- 1 | export ALGORAND_DATA=~/node/data 2 | 3 | export ESCROW_ADDRESS="GE25LQU674ZQFAYJM2RUCRXQRPWIMAEOUI26NHRDW77GEHRANYSRUTQYFE" 4 | export ESCROW_LOGICSIG="AiALAQMEBqqAygangMoGAozr3AWrgMoGrIDKBq2AygYyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=" 5 | 6 | export MANAGER_INDEX="13795367" 7 | export VALIDATOR_INDEX="13795370" 8 | export TOKEN1_INDEX="13795371" 9 | export TOKEN2_INDEX="13795372" 10 | export LIQUIDITY_TOKEN_INDEX="13795373" 11 | export TEST_ACCOUNT_ADDRESS="LZVKOKRULFYC7TIAOMA76VVPYWUJPRZ6V6FE7ZTMYAL7KNZNKR75HNA3HY" 12 | 13 | ./goal app call --app-id ${VALIDATOR_INDEX} --app-arg "str:s1" --app-arg "int:80888500" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn1.tx 14 | 15 | ./goal app call --app-id ${MANAGER_INDEX} --app-arg "str:s1" --app-arg "int:80888500" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn2.tx 16 | 17 | ./goal asset send -a 100000000 --assetid ${TOKEN1_INDEX} -f ${TEST_ACCOUNT_ADDRESS} -t ${ESCROW_ADDRESS} --out=txn3.tx 18 | 19 | cat txn1.tx txn2.tx txn3.tx > combinedtxns.tx 20 | 21 | ./goal clerk group -i combinedtxns.tx -o groupedtxns.tx 22 | ./goal clerk split -i groupedtxns.tx -o split.tx 23 | 24 | ./goal clerk sign -i split-0.tx -o signout-0.tx 25 | ./goal clerk sign -i split-1.tx -o signout-1.tx 26 | ./goal clerk sign -i split-2.tx -o signout-2.tx 27 | 28 | cat signout-0.tx signout-1.tx signout-2.tx > signout.tx 29 | 30 | ./goal clerk dryrun -t signout.tx --dryrun-dump --dryrun-dump-format json -o swap-t1-for-t2-dr.json -------------------------------------------------------------------------------- /scripts/dump_swap_t2_for_t1_txns.sh: -------------------------------------------------------------------------------- 1 | export ALGORAND_DATA=~/node/data 2 | 3 | export ESCROW_ADDRESS="C3BOJXVCV2NIJELWL7P3MW6FE6X6W6QKJSCNM5DKNTQ7Y7IMAPOBMLXD4U" 4 | export ESCROW_LOGICSIG="AiALAQMEBq3V1Qaq1dUGAsLZ4QWw1dUGsdXVBrLV1QYyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=" 5 | 6 | export MANAGER_INDEX="13986474" 7 | export VALIDATOR_INDEX="13986477" 8 | export TOKEN1_INDEX="13986480" 9 | export TOKEN2_INDEX="13986481" 10 | export LIQUIDITY_TOKEN_INDEX="13986482" 11 | export TEST_ACCOUNT_ADDRESS="SI36B6M4HKFJFRODQ4V3P726E56DVPMAF4A3HW5IIEA7IDDIW4IEPSE2UU" 12 | 13 | ./goal app call --app-id ${VALIDATOR_INDEX} --app-arg "str:s2" --app-arg "int:8088850" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn1.tx 14 | 15 | ./goal app call --app-id ${MANAGER_INDEX} --app-arg "str:s2" --app-arg "int:8088850" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn2.tx 16 | 17 | ./goal asset send -a 10000000 --assetid ${TOKEN1_INDEX} -f ${TEST_ACCOUNT_ADDRESS} -t ${ESCROW_ADDRESS} --out=txn3.tx 18 | 19 | cat txn1.tx txn2.tx txn3.tx > combinedtxns.tx 20 | 21 | ./goal clerk group -i combinedtxns.tx -o groupedtxns.tx 22 | ./goal clerk split -i groupedtxns.tx -o split.tx 23 | 24 | ./goal clerk sign -i split-0.tx -o signout-0.tx 25 | ./goal clerk sign -i split-1.tx -o signout-1.tx 26 | ./goal clerk sign -i split-2.tx -o signout-2.tx 27 | 28 | cat signout-0.tx signout-1.tx signout-2.tx > signout.tx 29 | 30 | ./goal clerk dryrun -t signout.tx --dryrun-dump --dryrun-dump-format json -o swap-t2-for-t1-dr.json -------------------------------------------------------------------------------- /scripts/dump_withdraw_liquidity_txns.sh: -------------------------------------------------------------------------------- 1 | export ALGORAND_DATA=~/node/data 2 | 3 | export ESCROW_ADDRESS="GE25LQU674ZQFAYJM2RUCRXQRPWIMAEOUI26NHRDW77GEHRANYSRUTQYFE" 4 | export ESCROW_LOGICSIG="AiALAQMEBqqAygangMoGAozr3AWrgMoGrIDKBq2AygYyBCISQAB6MgQjEkAAQDIEJBJAAAEAMwAQJRIzABghBBIQMwEQJRIQMwEYIQUSEDEWIQYSMRYjEhEQMRAkEhAxCTIDEhAxFTIDEhBCAGwzABAlEjMAGCEEEhAzARAlEhAzARghBRIQMRYhBhIQMRAkEhAxCTIDEhAxFTIDEhBCADkxGSISMQQhBw4QMRghBBIxGCEFEhEQMRAkEjEAMRQSEDEEIQcOEDERIQgSMREhCRIRMREhChIREBE=" 5 | 6 | export MANAGER_INDEX="13795367" 7 | export VALIDATOR_INDEX="13795370" 8 | export TOKEN1_INDEX="13795371" 9 | export TOKEN2_INDEX="13795372" 10 | export LIQUIDITY_TOKEN_INDEX="13795373" 11 | export TEST_ACCOUNT_ADDRESS="LZVKOKRULFYC7TIAOMA76VVPYWUJPRZ6V6FE7ZTMYAL7KNZNKR75HNA3HY" 12 | 13 | ./goal app call --app-id ${VALIDATOR_INDEX} --app-arg "str:w" --app-arg "str:1" --app-arg "str:1" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn1.tx 14 | 15 | ./goal app call --app-id ${MANAGER_INDEX} --app-arg "str:w" --app-arg "str:1" --app-arg "str:1" --from ${TEST_ACCOUNT_ADDRESS} --app-account ${ESCROW_ADDRESS} --out=txn2.tx 16 | 17 | ./goal asset send -a 500000000 --assetid ${LIQUIDITY_TOKEN_INDEX} -f ${TEST_ACCOUNT_ADDRESS} -t ${ESCROW_ADDRESS} --out=txn3.tx 18 | 19 | cat txn1.tx txn2.tx txn3.tx > combinedtxns.tx 20 | 21 | ./goal clerk group -i combinedtxns.tx -o groupedtxns.tx 22 | ./goal clerk split -i groupedtxns.tx -o split.tx 23 | 24 | ./goal clerk sign -i split-0.tx -o signout-0.tx 25 | ./goal clerk sign -i split-1.tx -o signout-1.tx 26 | ./goal clerk sign -i split-2.tx -o signout-2.tx 27 | 28 | cat signout-0.tx signout-1.tx signout-2.tx > signout.tx 29 | 30 | ./goal clerk dryrun -t signout.tx --dryrun-dump --dryrun-dump-format json -o withdraw-liq-dr.json -------------------------------------------------------------------------------- /tests/add_liquidity_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | from helpers import * 8 | 9 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 10 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 11 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 12 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 13 | TEST_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key(os.environ['TEST_ACCOUNT_PRIVATE_KEY']) 14 | TEST_ACCOUNT_ADDRESS = account.address_from_private_key(TEST_ACCOUNT_PRIVATE_KEY) 15 | 16 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 17 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 18 | 19 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 20 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 21 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 22 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 23 | LIQUIDITY_TOKEN_INDEX = int(os.environ['LIQUIDITY_TOKEN_INDEX']) 24 | 25 | TOKEN_AMOUNT = 500000000 26 | 27 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 28 | "x-api-key": ALGOD_TOKEN 29 | }) 30 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 31 | "x-api-key": INDEXER_TOKEN 32 | }) 33 | 34 | def wait_for_transaction(transaction_id): 35 | suggested_params = algod_client.suggested_params() 36 | algod_client.status_after_block(suggested_params.first + 4) 37 | result = indexer_client.search_transactions(txid=transaction_id) 38 | assert len(result['transactions']) == 1, result 39 | return result['transactions'][0] 40 | 41 | def add_liquidity(): 42 | print("Building add liquidity atomic transaction group...") 43 | 44 | encoded_app_args = [ 45 | bytes("a", "utf-8"), 46 | bytes("5", "utf-8"), 47 | ] 48 | 49 | # Transaction to Validator 50 | txn_1 = transaction.ApplicationCallTxn( 51 | sender=TEST_ACCOUNT_ADDRESS, 52 | sp=algod_client.suggested_params(), 53 | index=VALIDATOR_INDEX, 54 | on_complete=transaction.OnComplete.NoOpOC, 55 | accounts=[ESCROW_ADDRESS], 56 | app_args=encoded_app_args, 57 | ) 58 | 59 | # Transaction to Manager 60 | txn_2 = transaction.ApplicationCallTxn( 61 | sender=TEST_ACCOUNT_ADDRESS, 62 | sp=algod_client.suggested_params(), 63 | index=MANAGER_INDEX, 64 | on_complete=transaction.OnComplete.NoOpOC, 65 | accounts=[ESCROW_ADDRESS], 66 | app_args=encoded_app_args, 67 | ) 68 | 69 | # Transaction to send Token1 to Escrow 70 | txn_3 = transaction.AssetTransferTxn( 71 | sender=TEST_ACCOUNT_ADDRESS, 72 | sp=algod_client.suggested_params(), 73 | receiver=ESCROW_ADDRESS, 74 | amt=TOKEN_AMOUNT, 75 | index=TOKEN1_INDEX 76 | ) 77 | 78 | # Transaction to send Token2 to Escrow 79 | txn_4 = transaction.AssetTransferTxn( 80 | sender=TEST_ACCOUNT_ADDRESS, 81 | sp=algod_client.suggested_params(), 82 | receiver=ESCROW_ADDRESS, 83 | amt=TOKEN_AMOUNT, 84 | index=TOKEN2_INDEX 85 | ) 86 | 87 | # Get group ID and assign to transactions 88 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3, txn_4]) 89 | txn_1.group = gid 90 | txn_2.group = gid 91 | txn_3.group = gid 92 | txn_4.group = gid 93 | 94 | # Sign transactions 95 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 96 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 97 | stxn_3 = txn_3.sign(TEST_ACCOUNT_PRIVATE_KEY) 98 | stxn_4 = txn_4.sign(TEST_ACCOUNT_PRIVATE_KEY) 99 | 100 | # Broadcast the transactions 101 | signed_txns = [stxn_1, stxn_2, stxn_3, stxn_4] 102 | tx_id = algod_client.send_transactions(signed_txns) 103 | 104 | # Wait for transaction 105 | wait_for_transaction(tx_id) 106 | 107 | print(f"Add liquidity transaction sent from User to AlgoSwap successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 108 | 109 | print() 110 | 111 | if __name__ == "__main__": 112 | add_liquidity() 113 | 114 | get_token1_refund() 115 | get_token2_refund() 116 | get_liquidity_token_refund() -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | 8 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 9 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 10 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 11 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 12 | TEST_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key(os.environ['TEST_ACCOUNT_PRIVATE_KEY']) 13 | TEST_ACCOUNT_ADDRESS = account.address_from_private_key(TEST_ACCOUNT_PRIVATE_KEY) 14 | 15 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 16 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 17 | 18 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 19 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 20 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 21 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 22 | LIQUIDITY_TOKEN_INDEX = int(os.environ['LIQUIDITY_TOKEN_INDEX']) 23 | 24 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 25 | "x-api-key": ALGOD_TOKEN 26 | }) 27 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 28 | "x-api-key": INDEXER_TOKEN 29 | }) 30 | 31 | def get_token1_refund(): 32 | print("Attempting to get refund of Token 1 from Escrow...") 33 | 34 | encoded_app_args = [ 35 | bytes("r", "utf-8") 36 | ] 37 | 38 | # Calculate unused_token1 39 | unused_token1 = 0 40 | account_info = algod_client.account_info(TEST_ACCOUNT_ADDRESS) 41 | local_state = account_info['apps-local-state'] 42 | for block in local_state: 43 | if block['id'] == MANAGER_INDEX: 44 | for kvs in block['key-value']: 45 | decoded_key = base64.b64decode(kvs['key']) 46 | prefix_bytes = decoded_key[:2] 47 | prefix_key = prefix_bytes.decode('utf-8') 48 | addr_bytes = decoded_key[2:] 49 | b32_encoded_addr = base64.b32encode(addr_bytes).decode('utf-8') 50 | escrow_addr = encoding.encode_address(base64.b32decode(b32_encoded_addr)) 51 | 52 | if (prefix_key == "U1" and ESCROW_ADDRESS == escrow_addr): 53 | unused_token1 = int(kvs['value']['uint']) 54 | 55 | print(f"User unused Token 1 is {unused_token1}") 56 | 57 | if unused_token1 != 0: 58 | # Transaction to Validator 59 | txn_1 = transaction.ApplicationCallTxn( 60 | sender=TEST_ACCOUNT_ADDRESS, 61 | sp=algod_client.suggested_params(), 62 | index=VALIDATOR_INDEX, 63 | on_complete=transaction.OnComplete.NoOpOC, 64 | accounts=[ESCROW_ADDRESS], 65 | app_args=encoded_app_args 66 | ) 67 | 68 | # Transaction to Manager 69 | txn_2 = transaction.ApplicationCallTxn( 70 | sender=TEST_ACCOUNT_ADDRESS, 71 | sp=algod_client.suggested_params(), 72 | index=MANAGER_INDEX, 73 | on_complete=transaction.OnComplete.NoOpOC, 74 | accounts=[ESCROW_ADDRESS], 75 | app_args=encoded_app_args 76 | ) 77 | 78 | # Make LogicSig 79 | program = base64.b64decode(ESCROW_LOGICSIG) 80 | lsig = transaction.LogicSig(program) 81 | 82 | # Transaction to get refund of Token 1 from Escrow 83 | txn_3 = transaction.AssetTransferTxn( 84 | sender=ESCROW_ADDRESS, 85 | sp=algod_client.suggested_params(), 86 | receiver=TEST_ACCOUNT_ADDRESS, 87 | amt=unused_token1, 88 | index=TOKEN1_INDEX 89 | ) 90 | 91 | # Get group ID and assign to transactions 92 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 93 | txn_1.group = gid 94 | txn_2.group = gid 95 | txn_3.group = gid 96 | 97 | # Sign transactions 98 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 99 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 100 | stxn_3 = transaction.LogicSigTransaction(txn_3, lsig) 101 | 102 | # Broadcast the transactions 103 | signed_txns = [stxn_1, stxn_2, stxn_3] 104 | tx_id = algod_client.send_transactions(signed_txns) 105 | 106 | print(f"Got refund of Token 1 from AlgoSwap to User successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 107 | 108 | print() 109 | 110 | def get_token2_refund(): 111 | print("Attempting to get refund of Token 2 from Escrow...") 112 | 113 | encoded_app_args = [ 114 | bytes("r", "utf-8") 115 | ] 116 | 117 | # Calculate unused_token2 118 | unused_token2 = 0 119 | account_info = algod_client.account_info(TEST_ACCOUNT_ADDRESS) 120 | local_state = account_info['apps-local-state'] 121 | for block in local_state: 122 | if block['id'] == MANAGER_INDEX: 123 | for kvs in block['key-value']: 124 | decoded_key = base64.b64decode(kvs['key']) 125 | prefix_bytes = decoded_key[:2] 126 | prefix_key = prefix_bytes.decode('utf-8') 127 | addr_bytes = decoded_key[2:] 128 | b32_encoded_addr = base64.b32encode(addr_bytes).decode('utf-8') 129 | escrow_addr = encoding.encode_address(base64.b32decode(b32_encoded_addr)) 130 | 131 | if (prefix_key == "U2" and ESCROW_ADDRESS == escrow_addr): 132 | unused_token2 = int(kvs['value']['uint']) 133 | 134 | print(f"User unused Token 2 is {unused_token2}") 135 | 136 | if unused_token2 != 0: 137 | # Transaction to Validator 138 | txn_1 = transaction.ApplicationCallTxn( 139 | sender=TEST_ACCOUNT_ADDRESS, 140 | sp=algod_client.suggested_params(), 141 | index=VALIDATOR_INDEX, 142 | on_complete=transaction.OnComplete.NoOpOC, 143 | accounts=[ESCROW_ADDRESS], 144 | app_args=encoded_app_args 145 | ) 146 | 147 | # Transaction to Manager 148 | txn_2 = transaction.ApplicationCallTxn( 149 | sender=TEST_ACCOUNT_ADDRESS, 150 | sp=algod_client.suggested_params(), 151 | index=MANAGER_INDEX, 152 | on_complete=transaction.OnComplete.NoOpOC, 153 | accounts=[ESCROW_ADDRESS], 154 | app_args=encoded_app_args 155 | ) 156 | 157 | # Make LogicSig 158 | program = base64.b64decode(ESCROW_LOGICSIG) 159 | lsig = transaction.LogicSig(program) 160 | 161 | # Transaction to get refund of Token 2 from Escrow 162 | txn_3 = transaction.AssetTransferTxn( 163 | sender=ESCROW_ADDRESS, 164 | sp=algod_client.suggested_params(), 165 | receiver=TEST_ACCOUNT_ADDRESS, 166 | amt=unused_token2, 167 | index=TOKEN2_INDEX 168 | ) 169 | 170 | # Get group ID and assign to transactions 171 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 172 | txn_1.group = gid 173 | txn_2.group = gid 174 | txn_3.group = gid 175 | 176 | # Sign transactions 177 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 178 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 179 | stxn_3 = transaction.LogicSigTransaction(txn_3, lsig) 180 | 181 | # Broadcast the transactions 182 | signed_txns = [stxn_1, stxn_2, stxn_3] 183 | tx_id = algod_client.send_transactions(signed_txns) 184 | 185 | print(f"Got refund of Token 2 from AlgoSwap to User successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 186 | 187 | print() 188 | 189 | def get_liquidity_token_refund(): 190 | print("Attempting to get refund of liquidity tokens from Escrow...") 191 | 192 | encoded_app_args = [ 193 | bytes("r", "utf-8") 194 | ] 195 | 196 | # Calculate unused_liquidity 197 | unused_liquidity = 0 198 | results = algod_client.account_info(TEST_ACCOUNT_ADDRESS) 199 | local_state = results['apps-local-state'] 200 | for block in local_state: 201 | if block['id'] == MANAGER_INDEX: 202 | for kvs in block['key-value']: 203 | decoded_key = base64.b64decode(kvs['key']) 204 | prefix_bytes = decoded_key[:2] 205 | prefix_key = prefix_bytes.decode('utf-8') 206 | addr_bytes = decoded_key[2:] 207 | b32_encoded_addr = base64.b32encode(addr_bytes).decode('utf-8') 208 | escrow_addr = encoding.encode_address(base64.b32decode(b32_encoded_addr)) 209 | 210 | if (prefix_key == "UL" and ESCROW_ADDRESS == escrow_addr): 211 | unused_liquidity = int(kvs['value']['uint']) 212 | 213 | print(f"User unused liquidity is {unused_liquidity}") 214 | 215 | if unused_liquidity != 0: 216 | # Transaction to Validator 217 | txn_1 = transaction.ApplicationCallTxn( 218 | sender=TEST_ACCOUNT_ADDRESS, 219 | sp=algod_client.suggested_params(), 220 | index=VALIDATOR_INDEX, 221 | on_complete=transaction.OnComplete.NoOpOC, 222 | accounts=[ESCROW_ADDRESS], 223 | app_args=encoded_app_args 224 | ) 225 | 226 | # Transaction to Manager 227 | txn_2 = transaction.ApplicationCallTxn( 228 | sender=TEST_ACCOUNT_ADDRESS, 229 | sp=algod_client.suggested_params(), 230 | index=MANAGER_INDEX, 231 | on_complete=transaction.OnComplete.NoOpOC, 232 | accounts=[ESCROW_ADDRESS], 233 | app_args=encoded_app_args 234 | ) 235 | 236 | # Make LogicSig 237 | program = base64.b64decode(ESCROW_LOGICSIG) 238 | lsig = transaction.LogicSig(program) 239 | 240 | # Transaction to get Liquidity Token refund 241 | txn_3 = transaction.AssetTransferTxn( 242 | sender=ESCROW_ADDRESS, 243 | sp=algod_client.suggested_params(), 244 | receiver=TEST_ACCOUNT_ADDRESS, 245 | amt=unused_liquidity, 246 | index=LIQUIDITY_TOKEN_INDEX 247 | ) 248 | 249 | # Get group ID and assign to transactions 250 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 251 | txn_1.group = gid 252 | txn_2.group = gid 253 | txn_3.group = gid 254 | 255 | # Sign transactions 256 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 257 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 258 | stxn_3 = transaction.LogicSigTransaction(txn_3, lsig) 259 | 260 | # Broadcast the transactions 261 | signed_txns = [stxn_1, stxn_2, stxn_3] 262 | tx_id = algod_client.send_transactions(signed_txns) 263 | 264 | print(f"Got refund of liquidity tokens from AlgoSwap to User successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 265 | 266 | print() -------------------------------------------------------------------------------- /tests/swap_t1_for_t2_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | 8 | from helpers import * 9 | 10 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 11 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 12 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 13 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 14 | TEST_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key(os.environ['TEST_ACCOUNT_PRIVATE_KEY']) 15 | TEST_ACCOUNT_ADDRESS = account.address_from_private_key(TEST_ACCOUNT_PRIVATE_KEY) 16 | 17 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 18 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 19 | 20 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 21 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 22 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 23 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 24 | 25 | TOKEN1_AMOUNT = 10000000 26 | 27 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 28 | "x-api-key": ALGOD_TOKEN 29 | }) 30 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 31 | "x-api-key": INDEXER_TOKEN 32 | }) 33 | 34 | def wait_for_transaction(transaction_id): 35 | suggested_params = algod_client.suggested_params() 36 | algod_client.status_after_block(suggested_params.first + 4) 37 | result = indexer_client.search_transactions(txid=transaction_id) 38 | assert len(result['transactions']) == 1, result 39 | return result['transactions'][0] 40 | 41 | def swap_token1_for_token2(): 42 | print("Building swap Token 1 for Token 2 atomic transaction group...") 43 | 44 | encoded_app_args = [ 45 | bytes("s1", "utf-8"), 46 | (8088850).to_bytes(8, 'big') 47 | ] 48 | 49 | # Transaction to Validator 50 | txn_1 = transaction.ApplicationCallTxn( 51 | sender=TEST_ACCOUNT_ADDRESS, 52 | sp=algod_client.suggested_params(), 53 | index=VALIDATOR_INDEX, 54 | on_complete=transaction.OnComplete.NoOpOC, 55 | accounts=[ESCROW_ADDRESS], 56 | app_args=encoded_app_args 57 | ) 58 | 59 | # Transaction to Manager 60 | txn_2 = transaction.ApplicationCallTxn( 61 | sender=TEST_ACCOUNT_ADDRESS, 62 | sp=algod_client.suggested_params(), 63 | index=MANAGER_INDEX, 64 | on_complete=transaction.OnComplete.NoOpOC, 65 | accounts=[ESCROW_ADDRESS], 66 | app_args=encoded_app_args 67 | ) 68 | 69 | # Transaction to send Token 1 to Escrow 70 | txn_3 = transaction.AssetTransferTxn( 71 | sender=TEST_ACCOUNT_ADDRESS, 72 | sp=algod_client.suggested_params(), 73 | receiver=ESCROW_ADDRESS, 74 | amt=TOKEN1_AMOUNT, 75 | index=TOKEN1_INDEX 76 | ) 77 | 78 | # Get group ID and assign to transactions 79 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 80 | txn_1.group = gid 81 | txn_2.group = gid 82 | txn_3.group = gid 83 | 84 | # Sign transactions 85 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 86 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 87 | stxn_3 = txn_3.sign(TEST_ACCOUNT_PRIVATE_KEY) 88 | 89 | # Broadcast the transactions 90 | signed_txns = [stxn_1, stxn_2, stxn_3] 91 | tx_id = algod_client.send_transactions(signed_txns) 92 | 93 | # Wait for transaction 94 | wait_for_transaction(tx_id) 95 | 96 | print(f"Swap Token 1 for Token 2 transaction sent from User to AlgoSwap successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 97 | 98 | print() 99 | 100 | if __name__ == "__main__": 101 | swap_token1_for_token2() 102 | 103 | get_token2_refund() -------------------------------------------------------------------------------- /tests/swap_t2_for_t1_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | 8 | from helpers import * 9 | 10 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 11 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 12 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 13 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 14 | TEST_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key(os.environ['TEST_ACCOUNT_PRIVATE_KEY']) 15 | TEST_ACCOUNT_ADDRESS = account.address_from_private_key(TEST_ACCOUNT_PRIVATE_KEY) 16 | 17 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 18 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 19 | 20 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 21 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 22 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 23 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 24 | 25 | TOKEN2_AMOUNT = 10000000 26 | 27 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 28 | "x-api-key": ALGOD_TOKEN 29 | }) 30 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 31 | "x-api-key": INDEXER_TOKEN 32 | }) 33 | 34 | def wait_for_transaction(transaction_id): 35 | suggested_params = algod_client.suggested_params() 36 | algod_client.status_after_block(suggested_params.first + 4) 37 | result = indexer_client.search_transactions(txid=transaction_id) 38 | assert len(result['transactions']) == 1, result 39 | return result['transactions'][0] 40 | 41 | def swap_token2_for_token1(): 42 | print("Building swap Token 2 for Token 1 atomic transaction group...") 43 | 44 | encoded_app_args = [ 45 | bytes("s2", "utf-8"), 46 | (8088850).to_bytes(8, 'big') 47 | ] 48 | 49 | # Transaction to Validator 50 | txn_1 = transaction.ApplicationCallTxn( 51 | sender=TEST_ACCOUNT_ADDRESS, 52 | sp=algod_client.suggested_params(), 53 | index=VALIDATOR_INDEX, 54 | on_complete=transaction.OnComplete.NoOpOC, 55 | accounts=[ESCROW_ADDRESS], 56 | app_args=encoded_app_args 57 | ) 58 | 59 | # Transaction to Manager 60 | txn_2 = transaction.ApplicationCallTxn( 61 | sender=TEST_ACCOUNT_ADDRESS, 62 | sp=algod_client.suggested_params(), 63 | index=MANAGER_INDEX, 64 | on_complete=transaction.OnComplete.NoOpOC, 65 | accounts=[ESCROW_ADDRESS], 66 | app_args=encoded_app_args 67 | ) 68 | 69 | # Transaction to send Token 2 to Escrow 70 | txn_3 = transaction.AssetTransferTxn( 71 | sender=TEST_ACCOUNT_ADDRESS, 72 | sp=algod_client.suggested_params(), 73 | receiver=ESCROW_ADDRESS, 74 | amt=TOKEN2_AMOUNT, 75 | index=TOKEN2_INDEX 76 | ) 77 | 78 | # Get group ID and assign to transactions 79 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 80 | txn_1.group = gid 81 | txn_2.group = gid 82 | txn_3.group = gid 83 | 84 | # Sign transactions 85 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 86 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 87 | stxn_3 = txn_3.sign(TEST_ACCOUNT_PRIVATE_KEY) 88 | 89 | # Broadcast the transactions 90 | signed_txns = [stxn_1, stxn_2, stxn_3] 91 | tx_id = algod_client.send_transactions(signed_txns) 92 | 93 | # Wait for transaction 94 | wait_for_transaction(tx_id) 95 | 96 | print(f"Swap Token 2 for Token 1 transaction sent from User to AlgoSwap successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 97 | 98 | print() 99 | 100 | if __name__ == "__main__": 101 | swap_token2_for_token1() 102 | 103 | get_token1_refund() -------------------------------------------------------------------------------- /tests/withdraw_liquidity_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | 8 | from helpers import * 9 | 10 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 11 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 12 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 13 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 14 | TEST_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key(os.environ['TEST_ACCOUNT_PRIVATE_KEY']) 15 | TEST_ACCOUNT_ADDRESS = account.address_from_private_key(TEST_ACCOUNT_PRIVATE_KEY) 16 | 17 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 18 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 19 | 20 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 21 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 22 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 23 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 24 | LIQUIDITY_TOKEN_INDEX = int(os.environ['LIQUIDITY_TOKEN_INDEX']) 25 | 26 | TOKEN_AMOUNT = 500000000 27 | 28 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 29 | "x-api-key": ALGOD_TOKEN 30 | }) 31 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 32 | "x-api-key": INDEXER_TOKEN 33 | }) 34 | 35 | def wait_for_transaction(transaction_id): 36 | suggested_params = algod_client.suggested_params() 37 | algod_client.status_after_block(suggested_params.first + 4) 38 | result = indexer_client.search_transactions(txid=transaction_id) 39 | assert len(result['transactions']) == 1, result 40 | return result['transactions'][0] 41 | 42 | def withdraw_liquidity(): 43 | print("Building withdraw liquidity atomic transaction group...") 44 | 45 | encoded_app_args = [ 46 | bytes("w", "utf-8"), 47 | bytes("5", "utf-8"), 48 | bytes("5", "utf-8"), 49 | ] 50 | 51 | # Transaction to Validator 52 | txn_1 = transaction.ApplicationCallTxn( 53 | sender=TEST_ACCOUNT_ADDRESS, 54 | sp=algod_client.suggested_params(), 55 | index=VALIDATOR_INDEX, 56 | on_complete=transaction.OnComplete.NoOpOC, 57 | accounts=[ESCROW_ADDRESS], 58 | app_args=encoded_app_args, 59 | ) 60 | 61 | # Transaction to Manager 62 | txn_2 = transaction.ApplicationCallTxn( 63 | sender=TEST_ACCOUNT_ADDRESS, 64 | sp=algod_client.suggested_params(), 65 | index=MANAGER_INDEX, 66 | on_complete=transaction.OnComplete.NoOpOC, 67 | accounts=[ESCROW_ADDRESS], 68 | app_args=encoded_app_args, 69 | ) 70 | 71 | # Transaction to Escrow 72 | txn_3 = transaction.AssetTransferTxn( 73 | sender=TEST_ACCOUNT_ADDRESS, 74 | sp=algod_client.suggested_params(), 75 | receiver=ESCROW_ADDRESS, 76 | amt=TOKEN_AMOUNT, 77 | index=LIQUIDITY_TOKEN_INDEX 78 | ) 79 | 80 | # Get group ID and assign to transactions 81 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3]) 82 | txn_1.group = gid 83 | txn_2.group = gid 84 | txn_3.group = gid 85 | 86 | # Sign transactions 87 | stxn_1 = txn_1.sign(TEST_ACCOUNT_PRIVATE_KEY) 88 | stxn_2 = txn_2.sign(TEST_ACCOUNT_PRIVATE_KEY) 89 | stxn_3 = txn_3.sign(TEST_ACCOUNT_PRIVATE_KEY) 90 | 91 | # Broadcast the transactions 92 | signed_txns = [stxn_1, stxn_2, stxn_3] 93 | tx_id = algod_client.send_transactions(signed_txns) 94 | 95 | # Wait for transaction 96 | wait_for_transaction(tx_id) 97 | 98 | print(f"Withdraw liquidity transaction sent from User to AlgoSwap successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 99 | 100 | print() 101 | 102 | if __name__ == "__main__": 103 | withdraw_liquidity() 104 | 105 | get_token1_refund() 106 | get_token2_refund() 107 | get_liquidity_token_refund() -------------------------------------------------------------------------------- /tests/withdraw_protocol_fees_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import base64 3 | 4 | from algosdk.v2client import algod, indexer 5 | from algosdk import mnemonic, account, encoding 6 | from algosdk.future import transaction 7 | 8 | from helpers import * 9 | 10 | ALGOD_ENDPOINT = os.environ['ALGOD_ENDPOINT'] 11 | ALGOD_TOKEN = os.environ['ALGOD_TOKEN'] 12 | INDEXER_ENDPOINT = os.environ['INDEXER_ENDPOINT'] 13 | INDEXER_TOKEN = os.environ['INDEXER_TOKEN'] 14 | DEVELOPER_ACCOUNT_PRIVATE_KEY = mnemonic.to_private_key( 15 | os.environ['DEVELOPER_ACCOUNT_PRIVATE_KEY']) 16 | DEVELOPER_ACCOUNT_ADDRESS = account.address_from_private_key( 17 | DEVELOPER_ACCOUNT_PRIVATE_KEY) 18 | 19 | ESCROW_LOGICSIG = os.environ['ESCROW_LOGICSIG'] 20 | ESCROW_ADDRESS = os.environ['ESCROW_ADDRESS'] 21 | 22 | VALIDATOR_INDEX = int(os.environ['VALIDATOR_INDEX']) 23 | MANAGER_INDEX = int(os.environ['MANAGER_INDEX']) 24 | TOKEN1_INDEX = int(os.environ['TOKEN1_INDEX']) 25 | TOKEN2_INDEX = int(os.environ['TOKEN2_INDEX']) 26 | 27 | algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ENDPOINT, headers={ 28 | "x-api-key": ALGOD_TOKEN 29 | }) 30 | indexer_client = indexer.IndexerClient(INDEXER_TOKEN, INDEXER_ENDPOINT, headers={ 31 | "x-api-key": INDEXER_TOKEN 32 | }) 33 | 34 | def wait_for_transaction(transaction_id): 35 | suggested_params = algod_client.suggested_params() 36 | algod_client.status_after_block(suggested_params.first + 4) 37 | result = indexer_client.search_transactions(txid=transaction_id) 38 | assert len(result['transactions']) == 1, result 39 | return result['transactions'][0] 40 | 41 | def withdraw_protocol_fees(): 42 | print("Building withdraw protocol fees atomic transaction group...") 43 | 44 | encoded_app_args = [ 45 | bytes("p", "utf-8") 46 | ] 47 | 48 | # Get unclaimed TOKEN1 and TOKEN2 protocol fees 49 | protocol_unused_token1 = protocol_unused_token2 = 0 50 | account_info = algod_client.account_info(ESCROW_ADDRESS) 51 | local_state = account_info['apps-local-state'] 52 | for block in local_state: 53 | if block['id'] == MANAGER_INDEX: 54 | for kvs in block['key-value']: 55 | decoded_key = base64.b64decode(kvs['key']) 56 | prefix_bytes = decoded_key[:2] 57 | prefix_key = prefix_bytes.decode('utf-8') 58 | 59 | if (prefix_key == "P1"): 60 | protocol_unused_token1 = kvs['value']['uint'] 61 | elif (prefix_key == "P2"): 62 | protocol_unused_token2 = kvs['value']['uint'] 63 | 64 | print(f"Unused Protocol Fees is Token 1 = {protocol_unused_token1} and Token 2 = {protocol_unused_token2}") 65 | 66 | if protocol_unused_token1 != 0 and protocol_unused_token2 != 0: 67 | # Transaction to Validator 68 | txn_1 = transaction.ApplicationCallTxn( 69 | sender=DEVELOPER_ACCOUNT_ADDRESS, 70 | sp=algod_client.suggested_params(), 71 | index=VALIDATOR_INDEX, 72 | on_complete=transaction.OnComplete.NoOpOC, 73 | accounts=[ESCROW_ADDRESS], 74 | app_args=encoded_app_args 75 | ) 76 | 77 | # Transaction to Manager 78 | txn_2 = transaction.ApplicationCallTxn( 79 | sender=DEVELOPER_ACCOUNT_ADDRESS, 80 | sp=algod_client.suggested_params(), 81 | index=MANAGER_INDEX, 82 | on_complete=transaction.OnComplete.NoOpOC, 83 | accounts=[ESCROW_ADDRESS], 84 | app_args=encoded_app_args 85 | ) 86 | 87 | # Transaction to send Token 1 from Escrow to Developer 88 | txn_3 = transaction.AssetTransferTxn( 89 | sender=ESCROW_ADDRESS, 90 | sp=algod_client.suggested_params(), 91 | receiver=DEVELOPER_ACCOUNT_ADDRESS, 92 | amt=protocol_unused_token1, 93 | index=TOKEN1_INDEX 94 | ) 95 | 96 | # Transaction to send Token 2 from Escrow to Developer 97 | txn_4 = transaction.AssetTransferTxn( 98 | sender=ESCROW_ADDRESS, 99 | sp=algod_client.suggested_params(), 100 | receiver=DEVELOPER_ACCOUNT_ADDRESS, 101 | amt=protocol_unused_token2, 102 | index=TOKEN2_INDEX 103 | ) 104 | 105 | # Make Logicsig 106 | program = base64.b64decode(ESCROW_LOGICSIG) 107 | lsig = transaction.LogicSig(program) 108 | 109 | # Get group ID and assign to transactions 110 | gid = transaction.calculate_group_id([txn_1, txn_2, txn_3, txn_4]) 111 | txn_1.group = gid 112 | txn_2.group = gid 113 | txn_3.group = gid 114 | txn_4.group = gid 115 | 116 | # Sign transactions 117 | stxn_1 = txn_1.sign(DEVELOPER_ACCOUNT_PRIVATE_KEY) 118 | stxn_2 = txn_2.sign(DEVELOPER_ACCOUNT_PRIVATE_KEY) 119 | stxn_3 = transaction.LogicSigTransaction(txn_3, lsig) 120 | stxn_4 = transaction.LogicSigTransaction(txn_4, lsig) 121 | 122 | # Broadcast the transactions 123 | signed_txns = [stxn_1, stxn_2, stxn_3, stxn_4] 124 | tx_id = algod_client.send_transactions(signed_txns) 125 | 126 | # Wait for transaction 127 | wait_for_transaction(tx_id) 128 | 129 | print(f"Withdrew protocol fees for Token 1 and Token 2 from AlgoSwap to Developer successfully! Tx ID: https://testnet.algoexplorer.io/tx/{tx_id}") 130 | print() 131 | 132 | if __name__ == "__main__": 133 | withdraw_protocol_fees() --------------------------------------------------------------------------------