feat: 初始化项目

This commit is contained in:
gary 2022-04-11 15:02:10 +08:00
parent 687ff590e3
commit cceefd6371
736 changed files with 57879 additions and 30 deletions

11
.commitlintrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": ["@commitlint/config-conventional"],
"rules": {
"subject-case": [2, "always", "sentence-case"],
"type-enum": [
2,
"always",
["build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"]
]
}
}

28
.eslintrc Normal file
View File

@ -0,0 +1,28 @@
{
"extends": "@pancakeswap/eslint-config-pancake",
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"src/setupTests.[jt]s?(x)",
"src/testHelpers.[jt]s?(x)",
"**/*.test.[jt]s?(x)",
"rollup.config.js",
"src/mocks/**"
]
}
]
},
"overrides": [
{
"files": ["jest.setup.js"],
"env": {
"jest": true
},
"rules": {
"import/no-extraneous-dependencies": 0
}
}
]
}

7
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,7 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# @global-owner1 and @global-owner2 will be requested for
# review when someone opens a pull request.
* @RabbitDoge @hachiojidev @Chef-Chungus
packages/token-lists/ @RabbitDoge @hachiojidev @Chef-Chungus @ChefKai

4
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,4 @@
[ ] Before opening a pull request, please read the [contributing guidelines](https://github.com/pancakeswap/pancake-uikit/blob/master/CONTRIBUTING.md) first
[ ] If your PR is work in progress, open it as `draft`
[ ] Before requesting a review, all the checks need to pass
[ ] Explain what your PR does

23
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Build
on:
pull_request:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Run Build
run: yarn build

34
.github/workflows/deploy-storybook.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Deploy Storybook
on:
push:
branches:
- master
jobs:
storybook:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Build Storybook
run: yarn storybook:build
- name: Deploy Storybook
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./packages/pancake-uikit/storybook-static
publish_branch: gh-pages
user_name: "github-actions"
user_email: "github-actions@users.noreply.github.com"

29
.github/workflows/format.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Format
on:
pull_request:
jobs:
commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v2
with:
configFile: .commitlintrc.json
helpURL: https://docs.pancakeswap.finance/code/contributing#committing
code:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Run check
run: yarn format:check

23
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,23 @@
name: Lint
on:
pull_request:
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Run ESLint
run: yarn lint

22
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,22 @@
name: Tests
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Run Jest tests
run: yarn test

27
.github/workflows/tokenlist.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: Verify token lists
on:
pull_request:
paths:
- "packages/token-lists/src/tokens/**"
- "packages/token-lists/lists/**"
jobs:
verifyTokenLists:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2.1.2
with:
node-version: 14.x
- name: Install dependencies
run: yarn install
- name: Check if tokenlists were updated correctly
working-directory: ./packages/token-lists
run: yarn ci-check

19
.gitignore vendored Normal file
View File

@ -0,0 +1,19 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
# testing
coverage
# production
dist
storybook-static
# misc
.DS_Store
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"printWidth": 120
}

134
CHANGELOG.md Normal file
View File

@ -0,0 +1,134 @@
# 1.0.0 (2021-03-18)
### Bug Fixes
* **heading:** Sizes type was wrong ([#183](https://github.com/pancakeswap/pancake-toolkit/issues/183)) ([f5155e0](https://github.com/pancakeswap/pancake-toolkit/commit/f5155e0fc977fbfd686d1b6f7473ccc2a336af90))
* **lerna:** Config ([#15](https://github.com/pancakeswap/pancake-toolkit/issues/15)) ([8549ed7](https://github.com/pancakeswap/pancake-toolkit/commit/8549ed717d6393a554e146955790a840d11a250d))
* **uikit:** Accept float values for Slider ([#4](https://github.com/pancakeswap/pancake-toolkit/issues/4)) ([018d7e5](https://github.com/pancakeswap/pancake-toolkit/commit/018d7e5276e06cf880b2ce8f15f6eaa10e47f236))
* Format command ([#6](https://github.com/pancakeswap/pancake-toolkit/issues/6)) ([8cbc1b8](https://github.com/pancakeswap/pancake-toolkit/commit/8cbc1b866443047032cd040f6867f245e3d5b0c0))
* **icons:** Update viewbox ([#216](https://github.com/pancakeswap/pancake-toolkit/issues/216)) ([4420a1b](https://github.com/pancakeswap/pancake-toolkit/commit/4420a1be0d4ff41ba737bcc80eaea46c7b2a16b2))
* **slider:** Slider imports ([#222](https://github.com/pancakeswap/pancake-toolkit/issues/222)) ([ed2d693](https://github.com/pancakeswap/pancake-toolkit/commit/ed2d693d172a59b82e3209eed4d3e9a5f07f33b3))
* Add fix for older versions of Safari ([#123](https://github.com/pancakeswap/pancake-toolkit/issues/123)) ([002fecc](https://github.com/pancakeswap/pancake-toolkit/commit/002feccd076c3d662885305a5d57a183a83d557f))
* Add react router to menu links ([#46](https://github.com/pancakeswap/pancake-toolkit/issues/46)) ([6e0fa5b](https://github.com/pancakeswap/pancake-toolkit/commit/6e0fa5b8c67993e3f1537278a13da9bb4ebb9a17))
* Build and add build step to workflow ([#43](https://github.com/pancakeswap/pancake-toolkit/issues/43)) ([6f3fefc](https://github.com/pancakeswap/pancake-toolkit/commit/6f3fefc3cbd394f869bfad0422cb16c716204e31))
* bump react-dom to 17.x to fix react mismatch error in pancake-frontend ([#184](https://github.com/pancakeswap/pancake-toolkit/issues/184)) ([70941d1](https://github.com/pancakeswap/pancake-toolkit/commit/70941d177b6572e5879315d96beb5cee7b6e0a38))
* Menu and footer interactions ([f3f7f74](https://github.com/pancakeswap/pancake-toolkit/commit/f3f7f74bb86654fcfae5344f115d6d3fac129327))
* Menu on safari ([#53](https://github.com/pancakeswap/pancake-toolkit/issues/53)) ([5d5db86](https://github.com/pancakeswap/pancake-toolkit/commit/5d5db860c1648eb96a9b7637d9dad79edbab23b9))
* Re render on mobile ([#201](https://github.com/pancakeswap/pancake-toolkit/issues/201)) ([3802c15](https://github.com/pancakeswap/pancake-toolkit/commit/3802c153a7786fae9dc9eb20d5a45bed4a2c8c27))
* **menu:** Wrong type on Menu ([#120](https://github.com/pancakeswap/pancake-toolkit/issues/120)) ([a83ee2f](https://github.com/pancakeswap/pancake-toolkit/commit/a83ee2f14a2d1505c1574baa3582219715885530))
* **toggle:** Types ([#182](https://github.com/pancakeswap/pancake-toolkit/issues/182)) ([18186af](https://github.com/pancakeswap/pancake-toolkit/commit/18186afcb8e62af5beb999ab122cbf53b4dfb4fa))
* Copy to clipboard format ([#172](https://github.com/pancakeswap/pancake-toolkit/issues/172)) ([a77199c](https://github.com/pancakeswap/pancake-toolkit/commit/a77199cbbd871ca140be1446d5b688e3f85aba33))
* Export type for menu ([#111](https://github.com/pancakeswap/pancake-toolkit/issues/111)) ([0344771](https://github.com/pancakeswap/pancake-toolkit/commit/03447710dd2e973ad9967b402de8d6ea4cea13cc))
* Integration feedbacks into exchange ([#112](https://github.com/pancakeswap/pancake-toolkit/issues/112)) ([fc4627a](https://github.com/pancakeswap/pancake-toolkit/commit/fc4627a48f553c0a1e22141f286fb5e5ffcd9350))
* Layer visibilities ([#45](https://github.com/pancakeswap/pancake-toolkit/issues/45)) ([6fbe6d5](https://github.com/pancakeswap/pancake-toolkit/commit/6fbe6d518276cda61a20787003ba7f25f6990696))
* Logo link with react router ([d7b294b](https://github.com/pancakeswap/pancake-toolkit/commit/d7b294b172072c3d7e07b88377ef48601ac0be4f))
* Menu hide logic ([#156](https://github.com/pancakeswap/pancake-toolkit/issues/156)) ([2bda8a0](https://github.com/pancakeswap/pancake-toolkit/commit/2bda8a0efdfa040a17bc8f6d97f2bace8292c560))
* Table sort ([#175](https://github.com/pancakeswap/pancake-toolkit/issues/175)) ([67b97b4](https://github.com/pancakeswap/pancake-toolkit/commit/67b97b41d49bbfcc30ee7b52227186745dde0c61))
* **alert:** Spacing ([#155](https://github.com/pancakeswap/pancake-toolkit/issues/155)) ([d897ce8](https://github.com/pancakeswap/pancake-toolkit/commit/d897ce86bf7b6643f438b6c4d83339c6d3c8861d))
* Missing export ([#107](https://github.com/pancakeswap/pancake-toolkit/issues/107)) ([2c608db](https://github.com/pancakeswap/pancake-toolkit/commit/2c608dbcd9dcb82fe8fe0aef0dd0701ad89c3180))
* Radio and Breadcrumb ([#149](https://github.com/pancakeswap/pancake-toolkit/issues/149)) ([e98ea32](https://github.com/pancakeswap/pancake-toolkit/commit/e98ea3263009a2cb9be10fef19f2f3b7a7a9a3cb))
* **button:** Change import locations of button across WalletModal, Modal, Dropdown & Progress ([#72](https://github.com/pancakeswap/pancake-toolkit/issues/72)) ([779c8fa](https://github.com/pancakeswap/pancake-toolkit/commit/779c8fafcab07fbc5657c2537a6f8309cb43aee7))
* **button:** Line height ([#91](https://github.com/pancakeswap/pancake-toolkit/issues/91)) ([2911ff7](https://github.com/pancakeswap/pancake-toolkit/commit/2911ff72c203cec77605535ed559ac644c69ea24))
* **buttonmenu:** "as" not being forwarded correctly ([#99](https://github.com/pancakeswap/pancake-toolkit/issues/99)) ([6c9a8f1](https://github.com/pancakeswap/pancake-toolkit/commit/6c9a8f1d36838b75e44efa8546a7e07e2907ea13))
* **card:** Space props ([#145](https://github.com/pancakeswap/pancake-toolkit/issues/145)) ([8d7fdfa](https://github.com/pancakeswap/pancake-toolkit/commit/8d7fdfafdec89dd22fc43d6033daf6ef9e207a67))
* **radio:** Missing import ([#146](https://github.com/pancakeswap/pancake-toolkit/issues/146)) ([ec0fada](https://github.com/pancakeswap/pancake-toolkit/commit/ec0fada6caabb2bfb97feb9648bcb5a758ce4586))
* Price cake logo ([#54](https://github.com/pancakeswap/pancake-toolkit/issues/54)) ([9b0a379](https://github.com/pancakeswap/pancake-toolkit/commit/9b0a3793d468a8ca4549da88ca77092a93023ab4))
* Put scales at the theme root ([#34](https://github.com/pancakeswap/pancake-toolkit/issues/34)) ([c160770](https://github.com/pancakeswap/pancake-toolkit/commit/c160770e12d7f5139ae36b63c7b02aa412e2693b))
* SM media query value ([#22](https://github.com/pancakeswap/pancake-toolkit/issues/22)) ([8caae72](https://github.com/pancakeswap/pancake-toolkit/commit/8caae724d39c3ebf1ca4622e53a87a4bf179bf8f))
* Style on checkbox and text ([#24](https://github.com/pancakeswap/pancake-toolkit/issues/24)) ([27bf6cf](https://github.com/pancakeswap/pancake-toolkit/commit/27bf6cf40b1d9cd6d0ce5c8fbd366b2f0e456259))
* Style type ([0e3a769](https://github.com/pancakeswap/pancake-toolkit/commit/0e3a769e7abd785a241452b77a811ed4ce27a941))
* Tweak kit for exchange integration ([#98](https://github.com/pancakeswap/pancake-toolkit/issues/98)) ([19388c2](https://github.com/pancakeswap/pancake-toolkit/commit/19388c2664146cc4b659262ad06353ee2e7771fe))
* Update eslint rules for typescript ([#6](https://github.com/pancakeswap/pancake-toolkit/issues/6)) ([7f71224](https://github.com/pancakeswap/pancake-toolkit/commit/7f7122451ea2444c64bcdeae1e567d2cd2b4770a))
* Update root imports ([635b94a](https://github.com/pancakeswap/pancake-toolkit/commit/635b94a6272fc026d776433c293b83dbf490b31e))
* Update Spinner ([#140](https://github.com/pancakeswap/pancake-toolkit/issues/140)) ([8d2cb19](https://github.com/pancakeswap/pancake-toolkit/commit/8d2cb194bbaa29ec1e0f5731cd715c424adb79d6))
* Update spinner SVG according to new design ([#137](https://github.com/pancakeswap/pancake-toolkit/issues/137)) ([a9267e9](https://github.com/pancakeswap/pancake-toolkit/commit/a9267e966951e995f3a8eeeca3ff3929a4d33604))
* **modal:** Dark mode ([#85](https://github.com/pancakeswap/pancake-toolkit/issues/85)) ([616b76b](https://github.com/pancakeswap/pancake-toolkit/commit/616b76b56478efb548db9fb89edc77a6b289c5a9))
* **progress:** Issue when progress is 0 or 100% ([#88](https://github.com/pancakeswap/pancake-toolkit/issues/88)) ([071d95b](https://github.com/pancakeswap/pancake-toolkit/commit/071d95bad5f0c00ca51324f13cca6f6aa631d140))
* Wallet types ([#59](https://github.com/pancakeswap/pancake-toolkit/issues/59)) ([2fc1a86](https://github.com/pancakeswap/pancake-toolkit/commit/2fc1a863fc8048b9f9d0e79cc2cd0b873854f307))
### Features
* Add eslint package ([#7](https://github.com/pancakeswap/pancake-toolkit/issues/7)) ([0e0454e](https://github.com/pancakeswap/pancake-toolkit/commit/0e0454eb9a63e976934956dc5c66fbef2ce2017a))
* **button:** Add current color to start/end icons ([#210](https://github.com/pancakeswap/pancake-toolkit/issues/210)) ([a8901f8](https://github.com/pancakeswap/pancake-toolkit/commit/a8901f810d6baa1f0c96d3f7db898fa7a44dfdd2))
* **icons:** Arrow up ([#209](https://github.com/pancakeswap/pancake-toolkit/issues/209)) ([6e93077](https://github.com/pancakeswap/pancake-toolkit/commit/6e93077a430f36bd72c65cc27a3a80f76adb6f04))
* **icons:** Wait, Timer, Play icon ([#211](https://github.com/pancakeswap/pancake-toolkit/issues/211)) ([ba5e8be](https://github.com/pancakeswap/pancake-toolkit/commit/ba5e8beaf2791313f31475041ded08c5e1bbfef0))
* Add Metamask Svg Icon ([#212](https://github.com/pancakeswap/pancake-toolkit/issues/212)) ([6cea4fb](https://github.com/pancakeswap/pancake-toolkit/commit/6cea4fbb464703d25855c067d69ceda7b4f164ff))
* **progress:** Add flat variant ([#207](https://github.com/pancakeswap/pancake-toolkit/issues/207)) ([4d9e750](https://github.com/pancakeswap/pancake-toolkit/commit/4d9e75061f69d376a68be714ebb3f8bfd7381b86))
* Add a pancake spinner ([#136](https://github.com/pancakeswap/pancake-toolkit/issues/136)) ([eec4478](https://github.com/pancakeswap/pancake-toolkit/commit/eec4478e33e04a36c9a2819800df92adb98a2c61))
* Add copy to clipboard on the address ([#162](https://github.com/pancakeswap/pancake-toolkit/issues/162)) ([9906aad](https://github.com/pancakeswap/pancake-toolkit/commit/9906aad82a86689493cad378471f46ea68877b8c))
* Add Image component ([#131](https://github.com/pancakeswap/pancake-toolkit/issues/131)) ([dc95cfc](https://github.com/pancakeswap/pancake-toolkit/commit/dc95cfc945a14764ee277f6305b905325703e5a7))
* Add SafePal Wallet ([#202](https://github.com/pancakeswap/pancake-toolkit/issues/202)) ([da5c58c](https://github.com/pancakeswap/pancake-toolkit/commit/da5c58c33caffeead7b77b51272085b1336168ea))
* Add top level indicator on nav ([#205](https://github.com/pancakeswap/pancake-toolkit/issues/205)) ([b008cf9](https://github.com/pancakeswap/pancake-toolkit/commit/b008cf96d18cf8fd14c0b804d0a658c354d464d9))
* Add typography props to Text ([#188](https://github.com/pancakeswap/pancake-toolkit/issues/188)) ([9fb672d](https://github.com/pancakeswap/pancake-toolkit/commit/9fb672d42218d7c47033a306f73a499179ea4268))
* Box ([#189](https://github.com/pancakeswap/pancake-toolkit/issues/189)) ([d89db88](https://github.com/pancakeswap/pancake-toolkit/commit/d89db887de155806efbf264382f2b9b9e7478ae1))
* Create a new sidebar menu component ([afeb180](https://github.com/pancakeswap/pancake-toolkit/commit/afeb180b3e3f9d688c73808a64edbcaa9b754240))
* EasterEgg ([1783785](https://github.com/pancakeswap/pancake-toolkit/commit/1783785d9a0b81193216e194eb3bb358766adc99))
* Input border color ([#186](https://github.com/pancakeswap/pancake-toolkit/issues/186)) ([fa2cfa4](https://github.com/pancakeswap/pancake-toolkit/commit/fa2cfa4915b24c5510f8ec3a1f92057fd04b1ecc))
* **breadcrumbs:** Add mobile stylings ([#154](https://github.com/pancakeswap/pancake-toolkit/issues/154)) ([8773601](https://github.com/pancakeswap/pancake-toolkit/commit/87736018fddcdf28f085670498d35589eb1fe6fe))
* **button:** Success variant ([#96](https://github.com/pancakeswap/pancake-toolkit/issues/96)) ([8aae7be](https://github.com/pancakeswap/pancake-toolkit/commit/8aae7beaf2fb5575735fdfd546579a1aadcff002))
* **icon:** Arrow down ([#95](https://github.com/pancakeswap/pancake-toolkit/issues/95)) ([a6967ba](https://github.com/pancakeswap/pancake-toolkit/commit/a6967ba8e4f59d472fb7a6424c6710f4ff6590f1))
* **icons:** Additional icons ([#103](https://github.com/pancakeswap/pancake-toolkit/issues/103)) ([5f319a9](https://github.com/pancakeswap/pancake-toolkit/commit/5f319a9f73efe94c68fe8b7f17cc3ce34caa5fbb))
* **menu:** Add config option to control initial accordion open state ([#119](https://github.com/pancakeswap/pancake-toolkit/issues/119)) ([7242f63](https://github.com/pancakeswap/pancake-toolkit/commit/7242f6396bdde36a32e02373c727a70010933f1c))
* **menu:** Add pip to profiles without images ([#168](https://github.com/pancakeswap/pancake-toolkit/issues/168)) ([b39c2e2](https://github.com/pancakeswap/pancake-toolkit/commit/b39c2e23234f2acd1c45f7e367925fa14f3f7c21))
* **menu:** Add prop to make logo absolute url ([#114](https://github.com/pancakeswap/pancake-toolkit/issues/114)) ([778a50e](https://github.com/pancakeswap/pancake-toolkit/commit/778a50e08c28b6a71560985358c41677a9be836c))
* **menu:** Groups icon ([#144](https://github.com/pancakeswap/pancake-toolkit/issues/144)) ([4f7406e](https://github.com/pancakeswap/pancake-toolkit/commit/4f7406e02ed18d3dd179098bda54bf59af25747d))
* **menu:** Menu accordion remains open on click ([#126](https://github.com/pancakeswap/pancake-toolkit/issues/126)) ([c33ca14](https://github.com/pancakeswap/pancake-toolkit/commit/c33ca14918b54b1fda10f6477f0d6ea25820db58))
* **menu:** Profile avatar ([#159](https://github.com/pancakeswap/pancake-toolkit/issues/159)) ([2087ada](https://github.com/pancakeswap/pancake-toolkit/commit/2087adaf71c391c5ea8f7da927d405bae59b2242))
* **modal:** Add option to disable overlay click ([#158](https://github.com/pancakeswap/pancake-toolkit/issues/158)) ([fbe334a](https://github.com/pancakeswap/pancake-toolkit/commit/fbe334a23eb87a74c0fce127fce4d43bf3c096fb))
* **skeleton:** Add layout classes ([#151](https://github.com/pancakeswap/pancake-toolkit/issues/151)) ([02a0864](https://github.com/pancakeswap/pancake-toolkit/commit/02a0864c66b10c02070eca06d4c68f0d8597c1c1))
* **toasts:** Add ability to add action ([#164](https://github.com/pancakeswap/pancake-toolkit/issues/164)) ([cbba5a4](https://github.com/pancakeswap/pancake-toolkit/commit/cbba5a4704b64e7f962556c4a2a8de733d04ed8c))
* Breadcrumbs ([#134](https://github.com/pancakeswap/pancake-toolkit/issues/134)) ([afd4b0e](https://github.com/pancakeswap/pancake-toolkit/commit/afd4b0e2f0143d0b4a674f9fb985404f79eac2da))
* Generic table ([#109](https://github.com/pancakeswap/pancake-toolkit/issues/109)) ([fd8d4b9](https://github.com/pancakeswap/pancake-toolkit/commit/fd8d4b9d092b5bae5b4c49860b6c5e10eccbac1b))
* Input ([#86](https://github.com/pancakeswap/pancake-toolkit/issues/86)) ([415257c](https://github.com/pancakeswap/pancake-toolkit/commit/415257ca0341a91be6832efb2b2c6f8ad8de8bb5))
* Toast ([#138](https://github.com/pancakeswap/pancake-toolkit/issues/138)) ([ed5faeb](https://github.com/pancakeswap/pancake-toolkit/commit/ed5faebb82584abcc761018a6e7d6f5b15b3c68e))
* **nav:** Added NFT link ([#82](https://github.com/pancakeswap/pancake-toolkit/issues/82)) ([a70e7b9](https://github.com/pancakeswap/pancake-toolkit/commit/a70e7b99272824fb95483d50b15c2cfca8fe7908))
* Add new tag and icon ([#73](https://github.com/pancakeswap/pancake-toolkit/issues/73)) ([04cd6f8](https://github.com/pancakeswap/pancake-toolkit/commit/04cd6f8ef63f8c2a6882552d7fde577fc339f737))
* **button:** Icon button ([#69](https://github.com/pancakeswap/pancake-toolkit/issues/69)) ([1b34b28](https://github.com/pancakeswap/pancake-toolkit/commit/1b34b283de74513b4d059e5cf7b3382b83d99586))
* **checkbox:** Added scale ([#68](https://github.com/pancakeswap/pancake-toolkit/issues/68)) ([0c1d224](https://github.com/pancakeswap/pancake-toolkit/commit/0c1d22476eef1595c611614c62c3e60813f7ec24))
* **icon:** Arrow forward ([#70](https://github.com/pancakeswap/pancake-toolkit/issues/70)) ([4692bb9](https://github.com/pancakeswap/pancake-toolkit/commit/4692bb9bfe1b9a49f7a52e7bb0ccfc47dc839c19))
* Add cake price to menu ([#49](https://github.com/pancakeswap/pancake-toolkit/issues/49)) ([9cbc7c4](https://github.com/pancakeswap/pancake-toolkit/commit/9cbc7c4f7286e959f5274b883c93a6406589a997))
* Add color theme support in text props ([#61](https://github.com/pancakeswap/pancake-toolkit/issues/61)) ([2bbc200](https://github.com/pancakeswap/pancake-toolkit/commit/2bbc20097a1fdc1a48137186669b612daa0c88aa))
* Add dropdown submenus ([#51](https://github.com/pancakeswap/pancake-toolkit/issues/51)) ([d8592e6](https://github.com/pancakeswap/pancake-toolkit/commit/d8592e6625f210648a2268cdf967d7df974205e9))
* Add modal ([#26](https://github.com/pancakeswap/pancake-toolkit/issues/26)) ([2a86cda](https://github.com/pancakeswap/pancake-toolkit/commit/2a86cdaf4b5c46bd985d68f2e5db90d31be1845d))
* Add more states to buttons ([#36](https://github.com/pancakeswap/pancake-toolkit/issues/36)) ([d0d307e](https://github.com/pancakeswap/pancake-toolkit/commit/d0d307e23c051b090bdfc188de64c90b525d4924))
* Add new icons ([8e6062f](https://github.com/pancakeswap/pancake-toolkit/commit/8e6062fe339c2a29f6af5d0192fcc0718ef964d7))
* Add new icons ([#63](https://github.com/pancakeswap/pancake-toolkit/issues/63)) ([35860c7](https://github.com/pancakeswap/pancake-toolkit/commit/35860c7bbbc929bf848dd5c32f1431e699a6ca07))
* Add new props for Link component ([#66](https://github.com/pancakeswap/pancake-toolkit/issues/66)) ([28cbb64](https://github.com/pancakeswap/pancake-toolkit/commit/28cbb644733bbed3a7b6165101e375c53d8f0cea))
* Add the pancake eslint config ([fb3608d](https://github.com/pancakeswap/pancake-toolkit/commit/fb3608daeeee92b28f4e69d379fe705b42b3f79f))
* **nav:** New menu item ([#48](https://github.com/pancakeswap/pancake-toolkit/issues/48)) ([af92b47](https://github.com/pancakeswap/pancake-toolkit/commit/af92b474ef96aa192453443efdb9121ac8f04630))
* Add Link component ([#13](https://github.com/pancakeswap/pancake-toolkit/issues/13)) ([339d0ef](https://github.com/pancakeswap/pancake-toolkit/commit/339d0efd233eaa0db478a9c7e2cdeef6f5ef4e9a))
* Add reset.css component ([#9](https://github.com/pancakeswap/pancake-toolkit/issues/9)) ([855e648](https://github.com/pancakeswap/pancake-toolkit/commit/855e6488e1744acb500f74a5daed81ca42a22964))
* Add space props from styled-system ([#42](https://github.com/pancakeswap/pancake-toolkit/issues/42)) ([ce4cc9a](https://github.com/pancakeswap/pancake-toolkit/commit/ce4cc9a0d3656b9979622cabe9549d7477bc6da5))
* Add text component ([#12](https://github.com/pancakeswap/pancake-toolkit/issues/12)) ([1fe5fcd](https://github.com/pancakeswap/pancake-toolkit/commit/1fe5fcd5952eaadbd9d50e94e91060599b1af81e))
* Button ([#8](https://github.com/pancakeswap/pancake-toolkit/issues/8)) ([e5ab247](https://github.com/pancakeswap/pancake-toolkit/commit/e5ab247d89130c0bc09595be7fd20b6f284e6fed))
* ButtonMenu ([#10](https://github.com/pancakeswap/pancake-toolkit/issues/10)) ([1a85549](https://github.com/pancakeswap/pancake-toolkit/commit/1a855498803b6e838aec2bb386f6860a6a37967c))
* Card component ([#11](https://github.com/pancakeswap/pancake-toolkit/issues/11)) ([b99f5b6](https://github.com/pancakeswap/pancake-toolkit/commit/b99f5b6423775691353b5c7db12ad29d4521765b))
* Card ribbon ([#52](https://github.com/pancakeswap/pancake-toolkit/issues/52)) ([257a49c](https://github.com/pancakeswap/pancake-toolkit/commit/257a49c6706b67a08fbe92a160f9f91784895ab1))
* Checkbox ([#20](https://github.com/pancakeswap/pancake-toolkit/issues/20)) ([bb4c67e](https://github.com/pancakeswap/pancake-toolkit/commit/bb4c67e3f62a20f215b1ba86303abe10401d85d4))
* Heading ([#18](https://github.com/pancakeswap/pancake-toolkit/issues/18)) ([9e89a7a](https://github.com/pancakeswap/pancake-toolkit/commit/9e89a7afb32866d66ffa7a8ff8ce648fc1a00bb9))
* Layout ([#21](https://github.com/pancakeswap/pancake-toolkit/issues/21)) ([72162ed](https://github.com/pancakeswap/pancake-toolkit/commit/72162edc9b0d44ff3a6eddfae9550ed684a9f8f4))
* Navbar ([#40](https://github.com/pancakeswap/pancake-toolkit/issues/40)) ([f68ffa0](https://github.com/pancakeswap/pancake-toolkit/commit/f68ffa05362b2d80a86fb0abd5b0d84ca2f62a0b))
* Panel ([#32](https://github.com/pancakeswap/pancake-toolkit/issues/32)) ([907994c](https://github.com/pancakeswap/pancake-toolkit/commit/907994cc047f3fc0dfa64f49cee09d459a194d89))
* Progress ([#50](https://github.com/pancakeswap/pancake-toolkit/issues/50)) ([49a9c26](https://github.com/pancakeswap/pancake-toolkit/commit/49a9c26c613f1bd291e39e4b25b2823a282e81c2))
* **card:** Card states ([#25](https://github.com/pancakeswap/pancake-toolkit/issues/25)) ([67305eb](https://github.com/pancakeswap/pancake-toolkit/commit/67305eb1c216ce7419367433e153cf54e9fe85fd))
* **tags:** Outline variant ([#41](https://github.com/pancakeswap/pancake-toolkit/issues/41)) ([62bbb56](https://github.com/pancakeswap/pancake-toolkit/commit/62bbb56bd290625305f9936585156725f6429c37))
* **wallet:** Add ids for analytics ([#115](https://github.com/pancakeswap/pancake-toolkit/issues/115)) ([851de1b](https://github.com/pancakeswap/pancake-toolkit/commit/851de1bba96aa2156bfb87dac9bc0bf476492410))
* Svg and icons ([#28](https://github.com/pancakeswap/pancake-toolkit/issues/28)) ([6261102](https://github.com/pancakeswap/pancake-toolkit/commit/62611029d2787000599e00fb6a16a32c6a8b5c31))
* Tags ([#23](https://github.com/pancakeswap/pancake-toolkit/issues/23)) ([dbc4da2](https://github.com/pancakeswap/pancake-toolkit/commit/dbc4da29ef66e2be92602a6271c66255d7cd0099))
* Toggle ([#27](https://github.com/pancakeswap/pancake-toolkit/issues/27)) ([b770cb3](https://github.com/pancakeswap/pancake-toolkit/commit/b770cb335e3e88c2c5f045a2ae1bd360b0c2afba))
### Performance Improvements
* Memo menu components ([ba47f13](https://github.com/pancakeswap/pancake-toolkit/commit/ba47f13c1f833015375306c312b0fc6a7ef35b97))
### Reverts
* Revert "feat: Input border color (#186)" (#187) ([e3ab69a](https://github.com/pancakeswap/pancake-toolkit/commit/e3ab69a1040ceae3f5e65d45d0229adefdf2ccd2)), closes [#186](https://github.com/pancakeswap/pancake-toolkit/issues/186) [#187](https://github.com/pancakeswap/pancake-toolkit/issues/187)
* Revert "chore: Add 4 telegram groups" ([3de730c](https://github.com/pancakeswap/pancake-toolkit/commit/3de730cc067aaedb6b123d3018aa3d8de7f2bb84))

View File

@ -1,39 +1,25 @@
# highcity-toolkit
# Pancake Toolkit
#### 介绍
{**以下是 Gitee 平台说明,您可以替换此简介**
Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN。专为开发者提供稳定、高效、安全的云端软件开发协作平台
无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
This repository is a monorepo manage with [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces/) and [Lerna](https://lerna.js.org/).
#### 软件架构
软件架构说明
## Packages
- [pancake-uikit](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/pancake-uikit) : React components used to build the Pancake UI
- [eslint-config-pancake](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake) : An ESLint config for pancake, with Typescript and Prettier support
#### 安装教程
## How to use
1. xxxx
2. xxxx
3. xxxx
Clone the repository
#### 使用说明
```
git clone git@github.com:pancakeswap/pancake-toolkit.git
```
1. xxxx
2. xxxx
3. xxxx
Run yarn at the root of the workspace
#### 参与贡献
```
cd pancake-toolkit
yarn
```
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
Then, refer to the readme of each project.

13
lerna.json Normal file
View File

@ -0,0 +1,13 @@
{
"packages": ["packages/*"],
"version": "independent",
"npmClient": "yarn",
"workspaces": true,
"ignoreChanges": ["**/__tests__/**", "**/*.md", "**/.storybook/**"],
"command": {
"version": {
"conventionalCommits": true,
"message": "chore(release): Publish"
}
}
}

66
package.json Normal file
View File

@ -0,0 +1,66 @@
{
"name": "pancake-toolkit",
"version": "1.0.0",
"description": "Pancake frontend packages",
"private": true,
"workspaces": [
"packages/*"
],
"repository": {
"type": "git",
"url": "git+https://github.com/pancakeswap/pancake-toolkit.git"
},
"license": "ISC",
"bugs": {
"url": "https://github.com/pancakeswap/pancake-toolkit/issues"
},
"homepage": "https://github.com/pancakeswap/pancake-toolkit#readme",
"scripts": {
"build": "lerna run build",
"test": "lerna run test",
"lint": "lerna run lint",
"format:check": "lerna run format:check",
"storybook:build": "lerna run storybook:build",
"release": "yarn build && yarn lerna publish"
},
"husky": {
"hooks": {
"pre-commit": "yarn format:check",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"devDependencies": {
"@babel/core": "^7.14.3",
"@babel/preset-env": "^7.14.4",
"@commitlint/cli": "^12.1.1",
"@commitlint/config-conventional": "^12.1.1",
"@pancakeswap/eslint-config-pancake": "1.2.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-typescript": "^8.2.1",
"@rollup/plugin-url": "^6.0.0",
"@storybook/addon-a11y": "^6.2.9",
"@storybook/addon-actions": "^6.2.9",
"@storybook/addon-essentials": "^6.2.9",
"@storybook/addon-links": "^6.2.9",
"@storybook/react": "^6.2.9",
"@types/react": "^17.0.5",
"@types/react-router-dom": "^5.1.7",
"@types/react-transition-group": "^4.4.1",
"@types/styled-components": "^5.1.9",
"@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0",
"babel-loader": "^8.2.2",
"babel-plugin-styled-components": "^1.12.0",
"eslint": "^7.26.0",
"husky": "4.3.5",
"jest": "^26.6.3",
"lerna": "^4.0.0",
"prettier": "^2.3.0",
"react-is": "^17.0.2",
"rollup": "^2.47.0",
"themeprovider-storybook": "^1.7.1",
"ts-jest": "^26.5.6",
"tslib": "^2.2.0",
"typescript": "^4.2.4"
}
}

View File

@ -0,0 +1,33 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.2.0](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/compare/@pancakeswap/eslint-config-pancake@1.1.0...@pancakeswap/eslint-config-pancake@1.2.0) (2021-07-09)
### Features
* Migrate ad-hoc FE rules to eslint-config ([#187](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/issues/187)) ([75baf17](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/commit/75baf175c8316fdfc549bc99e2bc38d65b18c5b6))
# 1.1.0 (2021-05-07)
### Features
* Add eslint package ([#7](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/issues/7)) ([0e0454e](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/commit/0e0454eb9a63e976934956dc5c66fbef2ce2017a))
## [1.0.1](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/compare/@pancakeswap-libs/eslint-config-pancake@1.0.1...@pancakeswap-libs/eslint-config-pancake@1.0.1) (2021-03-19)
### Features
* Add eslint package ([#7](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/issues/7)) ([0e0454e](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake/commit/0e0454eb9a63e976934956dc5c66fbef2ce2017a))

View File

@ -0,0 +1,15 @@
# eslint-config-pancake
Pancake Eslint config with:
- Airbnb config
- Typescript
- Prettier
## Usage
```
npx install-peerdeps --dev @pancakeswap/eslint-config-pancake
```
Add `"extends": "@pancakeswap/eslint-config-pancake"` to your eslint config file.

View File

@ -0,0 +1,58 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2017,
ecmaFeatures: {
jsx: true,
},
},
env: {
es6: true,
browser: true,
},
settings: {
"import/resolver": {
node: {
extensions: [".js", ".ts", ".jsx", ".tsx"],
},
},
"import/extensions": [".js", ".ts", ".jsx", ".tsx"],
},
extends: [
"airbnb",
"airbnb/hooks",
"prettier",
"prettier/react",
"prettier/@typescript-eslint",
"plugin:@typescript-eslint/recommended",
],
rules: {
// Typescript
"@typescript-eslint/no-unused-vars": "warn",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": ["warn"],
"no-shadow": "off",
"@typescript-eslint/no-shadow": ["error"],
"no-console": ["warn", { allow: ["info", "warn", "error"] }],
"no-plusplus": 0,
"prefer-destructuring": ["warn", { object: true, array: false }],
"no-underscore-dangle": 0,
// React
"react/jsx-filename-extension": ["error", { extensions: [".tsx"] }],
"react/prop-types": 0,
"react/jsx-props-no-spreading": 0,
"react/no-multi-comp": 0,
"import/extensions": [
"error",
"ignorePackages",
{
js: "never",
mjs: "never",
jsx: "never",
ts: "never",
tsx: "never",
},
],
},
};

View File

@ -0,0 +1,30 @@
{
"name": "@pancakeswap/eslint-config-pancake",
"version": "1.2.0",
"description": "Eslint config for PancakeSwap",
"main": "lib/index.js",
"files": [
"lib"
],
"repository": "https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/eslint-config-pancake",
"license": "MIT",
"author": "RabbitDoge",
"private": false,
"dependencies": {
"@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.7.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.0.0"
},
"peerDependencies": {
"eslint": "^7.2.0",
"prettier": "^2.1.2"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# 0.1.0 (2021-06-14)
### Features
* New Package: Profile SDK ([#80](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/pancake-profile-sdk/issues/80)) ([13dda1e](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/pancake-profile-sdk/commit/13dda1e43c6528dd7a1812c8a860f6f242148062))

View File

@ -0,0 +1,223 @@
# Pancakeswap Profile SDK
This package provides some handy functions to retrieve data for Pancakeswap Profile system.
If you're looking for React-ready solution - take a look at the [profile-hook](https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/pancake-profile-hook).
##### Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Initialization](#initialization)
- [getUsername](#getUsername)
- [getTeam](#getTeam)
- [getProfile](#getProfile)
- [getAchievements](#getAchievements)
- [Roadmap](#roadmap)
## Installation
Install `@pancakeswap/profile-sdk` into your project with npm:
```bash
npm install @pancakeswap/profile-sdk --save
```
or yarn:
```bash
yarn add @pancakeswap/profile-sdk
```
This package requires `web3` to be installed in your project. If you're using TypeScript you also should install `web3-eth-contract` and `web3-utils` to avoid type errors, although depending on your TypeScript and ESlint configuration you might skip installing those (since they are sub-packages of web3).
```bash
# npm
npm install web3 --save
# yarn
yarn add web3
```
## Usage
### Initialization
First set is to initialize the SDK with the following:
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
const pancakeSdk = new PancakeProfileSdk();
```
You can pass optional arguments to the constructor:
- `web3` - custom web3 instance if you want to use web3 instance with custom configuration, if not provided defaults to the following configuration:
- HTTP provider with timeout of 10 seconds
- a random node is chosen on initialization from the [list of RPC nodes](src/utils/getRpcUrl.ts)
- the rest of the configuration is default Web3
- `chainId` - what chain ID to use, if not provided defaults to `56`
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
import Web3 from "web3";
const httpProvider = new Web3.providers.HttpProvider("https://mycustomnode.com", {
timeout: 5000,
});
const myWeb3 = new Web3(httpProvider);
const pancakeSdk = new PancakeProfileSdk(myWeb3, 97);
```
### getUsername
Returns username for a given address. If the address does not have a profile or there is an error - returns empty string `""`.
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
const pancakeSdk = new PancakeProfileSdk();
const username = pancakeSdk.getUsername("0x123456789");
console.log(username); // "Matatabi"
```
### getTeam
Returns team information for the team ID. In case of network error returns null. Note that at the moment `points` will return `0` for all teams (total team points will be calculated soon).
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
const pancakeSdk = new PancakeProfileSdk();
const team = pancakeSdk.getTeam(1);
console.log(team);
// {
// id: 1,
// name: "Syrup Storm",
// description: "The storm's a-comin! Watch out! These bulls are stampeding in a syrupy surge!",
// isJoinable: true,
// users: 55123;
// points: 182500;
// images: images: {
// lg: "syrup-storm-lg.png",
// md: "syrup-storm-md.png",
// sm: "syrup-storm-sm.png",
// alt: "syrup-storm-alt.png",
// ipfs: "https://gateway.pinata.cloud/ipfs/QmXKzSojwzYjtDCVgR6mVx7w7DbyYpS7zip4ovJB9fQdMG/syrup-storm.png",
// },
// background: syrup-storm-bg.svg;
// textColor: "#191326";
// }
```
### getProfile
Returns full profile data for a given address. Under the hood retrieves username and team data using `getUsername` and `getTeam` and combines it with data from the profile contract. If address does not have a profile - returns `{ hasRegistered: false, profile: null }`. At the moment does not retrieve achievements (see [getAchievements](#getAchievements)).
It also sets `profile_${address}` cookie containing username and avatar (now only for pancakeswap.finance domain, maybe configurable in future versions)
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
const pancakeSdk = new PancakeProfileSdk();
const profile = pancakeSdk.getProfile("0x123456789");
console.log(profile);
// {
// hasRegistered: true
// profile: {
// userId: 6173,
// points: 2500,
// teamId: 1,
// nftAddress: "0x11111111",
// tokenId: 15,
// isActive: true,
// username: "Matatabi",
// nft: {
// name: "Hiccup",
// description: "Oopsie daisy! Hiccup's had a bit of an accident. Poor little fella.",
// images: {
// lg: "hiccup-lg.png",
// md: "hiccup-md.png",
// sm: "hiccup-sm.png",
// ipfs: "https://gateway.pinata.cloud/ipfs/QmQ6EE6gkVzAQUdQLLM7CyrnME6LZHCoy92ZERW8HXmyjw/hiccup.png",
// },
// sortOrder: 999,
// identifier: 'hiccup'
// type: 'pancake',
// variationId: 10
// },
// team: {
// id: 1,
// name: "Syrup Storm",
// description: "The storm's a-comin! Watch out! These bulls are stampeding in a syrupy surge!",
// isJoinable: true,
// users: 55123,
// points: 182500,
// images: images: {
// lg: "syrup-storm-lg.png",
// md: "syrup-storm-md.png",
// sm: "syrup-storm-sm.png",
// alt: "syrup-storm-alt.png",
// ipfs: "https://gateway.pinata.cloud/ipfs/QmXKzSojwzYjtDCVgR6mVx7w7DbyYpS7zip4ovJB9fQdMG sy rup-storm.png",
// },
// background: syrup-storm-bg.svg,
// textColor: "#191326"
// },
// hasRegistered: true
// }
// }
```
### getAchievements
Returns array of achievements for a given address. If address has no achievements or no profile at all - returns empty array `[]`.
```js
import PancakeProfileSdk from "@pancakeswap/profile-sdk";
const pancakeSdk = new PancakeProfileSdk();
const achievements = pancakeSdk.getAchievements("0x123456789");
console.log(achievements);
// [
// {
// id: "511080000",
// type: "ifo",
// address: "0x123456789",
// title: {
// id: 999,
// fallback: `IFO Shopper: Belt`,
// data: {
// name: "Belt",
// },
// },
// description: {
// id: 999,
// fallback: `Committed more than $5 worth of LP in the Belt IFO`,
// data: {
// name: "Belt",
// },
// },
// badge: "ifo-belt.svg",
// points: 200,
// },
// {
// id: "512010010",
// type: "teambattle",
// address: "0x123456789",
// title: "Easter Participant: Silver",
// badge: "easter-participant-silver.svg",
// points: 500,
// },
// ];
```
## Roadmap
Current version of this SDK is 90% copy of existing from [pancake-frontend](https://github.com/pancakeswap/pancake-frontend) repo. There are several improvements to be made in the future versions of this SDK:
- [ ] Better error handling (common bad status codes or broken internet connection)
- [ ] Allow username & avatar cookie to be configurable or optional
- [ ] Validate addresses with regex and don't attempt to fetch data if address is not valid
- [ ] NodeJS support. Currently it works out of the box only in browser. Need to research different options for cross-fetch and choose the one that provides less friction and increases bundle size the least.

View File

@ -0,0 +1,6 @@
module.exports = {
presets: [
["@babel/preset-env", { targets: { node: "current" } }],
"@babel/preset-typescript",
],
};

View File

@ -0,0 +1,10 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest",
"^.+\\.(js|jsx)$": "babel-jest",
},
setupFilesAfterEnv: ["./jest.setup.js"],
collectCoverageFrom: ["**/*.ts", "!./src/index.ts", "!**/node_modules/**", "!**/dist/**"],
};

View File

@ -0,0 +1,12 @@
// This ensures you can use `window.fetch()` in Jest tests.
import fetch from "node-fetch";
import { server } from "./src/mocks/server";
global.fetch = fetch;
// Establish API mocking before all tests.
beforeAll(() => server.listen());
// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());
// Clean up after the tests are finished.
afterAll(() => server.close());

View File

@ -0,0 +1,48 @@
{
"name": "@pancakeswap/profile-sdk",
"version": "0.1.0",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"license": "MIT",
"repository": "https://github.com/pancakeswap/pancake-toolkit/tree/master/packages/pancake-profile-sdk",
"scripts": {
"build": "rm -rf ./dist && rollup -c && tsc -d --emitDeclarationOnly --declarationDir dist",
"start": "yarn build && node ./dist",
"test": "jest",
"test:coverage": "jest --collectCoverage",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"format:check": "prettier --check --loglevel error 'src/**/*.{js,jsx,ts,tsx}'",
"format:write": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'"
},
"devDependencies": {
"@babel/preset-env": "^7.13.15",
"@babel/preset-typescript": "^7.13.0",
"@rollup/plugin-commonjs": "^18.0.0",
"@rollup/plugin-image": "^2.0.6",
"@rollup/plugin-node-resolve": "^11.2.1",
"@types/jest": "^26.0.22",
"@types/node": "^14.14.39",
"babel-jest": "^26.6.3",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"jest": "^26.6.3",
"msw": "^0.28.1",
"node-fetch": "^2.6.1",
"ts-jest": "^26.5.4",
"web3": "^1.3.5",
"web3-eth-contract": "^1.3.5",
"web3-utils": "^1.3.5"
},
"peerDependencies": {
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"web3": ">=1.3.5",
"web3-eth-contract": "=>1.3.5",
"web3-utils": ">=1.3.5"
},
"dependencies": {
"@types/js-cookie": "^2.2.6",
"js-cookie": "^2.2.1"
}
}

View File

@ -0,0 +1,16 @@
import typescript from "@rollup/plugin-typescript";
import json from "@rollup/plugin-json";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import image from "@rollup/plugin-image";
import pkg from "./package.json";
export default {
input: "src/index.ts",
output: [
{ file: pkg.main, format: "cjs" },
{ file: pkg.module, format: "es" },
],
plugins: [json(), commonjs(), nodeResolve(), typescript(), image()],
external: ["web3", "web3-eth-contract", "web3-utils", "graphql", "graphql-request"],
};

View File

@ -0,0 +1,191 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [{ "name": "_name", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_tokenId", "type": "uint256" }],
"name": "getApproved",
"outputs": [{ "name": "_approved", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_tokenId", "type": "uint256" }
],
"name": "approve",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "implementsERC721",
"outputs": [{ "name": "_implementsERC721", "type": "bool" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [{ "name": "_totalSupply", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_from", "type": "address" },
{ "name": "_to", "type": "address" },
{ "name": "_tokenId", "type": "uint256" }
],
"name": "transferFrom",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "name": "_owner", "type": "address" },
{ "name": "_index", "type": "uint256" }
],
"name": "tokenOfOwnerByIndex",
"outputs": [{ "name": "_tokenId", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_tokenId", "type": "uint256" }],
"name": "ownerOf",
"outputs": [{ "name": "_owner", "type": "address" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_tokenId", "type": "uint256" }],
"name": "tokenMetadata",
"outputs": [{ "name": "_infoUrl", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "_balance", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_owner", "type": "address" },
{ "name": "_tokenId", "type": "uint256" },
{ "name": "_approvedAddress", "type": "address" },
{ "name": "_metadata", "type": "string" }
],
"name": "mint",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [{ "name": "_symbol", "type": "string" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_tokenId", "type": "uint256" }
],
"name": "transfer",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "numTokensTotal",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }],
"name": "getOwnerTokens",
"outputs": [{ "name": "_tokenIds", "type": "uint256[]" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "_to", "type": "address" },
{ "indexed": true, "name": "_tokenId", "type": "uint256" }
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "_from", "type": "address" },
{ "indexed": true, "name": "_to", "type": "address" },
{ "indexed": false, "name": "_tokenId", "type": "uint256" }
],
"name": "Transfer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "name": "_owner", "type": "address" },
{ "indexed": true, "name": "_approved", "type": "address" },
{ "indexed": false, "name": "_tokenId", "type": "uint256" }
],
"name": "Approval",
"type": "event"
},
{
"inputs": [{ "internalType": "uint256", "name": "tokenId", "type": "uint256" }],
"name": "tokenURI",
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
"stateMutability": "view",
"type": "function"
}
]

View File

@ -0,0 +1,717 @@
[
{
"inputs": [
{
"internalType": "contract IBEP20",
"name": "_cakeToken",
"type": "address"
},
{
"internalType": "uint256",
"name": "_numberCakeToReactivate",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_numberCakeToRegister",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_numberCakeToUpdate",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "teamId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "teamName",
"type": "string"
}
],
"name": "TeamAdd",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "teamId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "numberPoints",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "campaignId",
"type": "uint256"
}
],
"name": "TeamPointIncrease",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "oldTeamId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "newTeamId",
"type": "uint256"
}
],
"name": "UserChangeTeam",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "teamId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "nftAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "UserNew",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "teamId",
"type": "uint256"
}
],
"name": "UserPause",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "numberPoints",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "campaignId",
"type": "uint256"
}
],
"name": "UserPointIncrease",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address[]",
"name": "userAddresses",
"type": "address[]"
},
{
"indexed": false,
"internalType": "uint256",
"name": "numberPoints",
"type": "uint256"
},
{
"indexed": true,
"internalType": "uint256",
"name": "campaignId",
"type": "uint256"
}
],
"name": "UserPointIncreaseMultiple",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "teamId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "nftAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "UserReactivate",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "userAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "nftAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "UserUpdate",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "NFT_ROLE",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "POINT_ROLE",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "SPECIAL_ROLE",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_nftAddress", "type": "address" }
],
"name": "addNftAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "string", "name": "_teamName", "type": "string" },
{ "internalType": "string", "name": "_teamDescription", "type": "string" }
],
"name": "addTeam",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "cakeToken",
"outputs": [
{ "internalType": "contract IBEP20", "name": "", "type": "address" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_userAddress", "type": "address" },
{ "internalType": "uint256", "name": "_newTeamId", "type": "uint256" }
],
"name": "changeTeam",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_amount", "type": "uint256" }
],
"name": "claimFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" },
{ "internalType": "address", "name": "_nftAddress", "type": "address" },
{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }
],
"name": "createProfile",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" }
],
"name": "getRoleAdmin",
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
{ "internalType": "uint256", "name": "index", "type": "uint256" }
],
"name": "getRoleMember",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" }
],
"name": "getRoleMemberCount",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" }
],
"name": "getTeamProfile",
"outputs": [
{ "internalType": "string", "name": "", "type": "string" },
{ "internalType": "string", "name": "", "type": "string" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "bool", "name": "", "type": "bool" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_userAddress", "type": "address" }
],
"name": "getUserProfile",
"outputs": [
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "bool", "name": "", "type": "bool" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "hasRegistered",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "hasRole",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" },
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" },
{ "internalType": "uint256", "name": "_campaignId", "type": "uint256" }
],
"name": "increaseTeamPoints",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_userAddress", "type": "address" },
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" },
{ "internalType": "uint256", "name": "_campaignId", "type": "uint256" }
],
"name": "increaseUserPoints",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "_userAddresses",
"type": "address[]"
},
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" },
{ "internalType": "uint256", "name": "_campaignId", "type": "uint256" }
],
"name": "increaseUserPointsMultiple",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" }
],
"name": "makeTeamJoinable",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" }
],
"name": "makeTeamNotJoinable",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "numberActiveProfiles",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "numberCakeToReactivate",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "numberCakeToRegister",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "numberCakeToUpdate",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "numberTeams",
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "address", "name": "", "type": "address" },
{ "internalType": "uint256", "name": "", "type": "uint256" },
{ "internalType": "bytes", "name": "", "type": "bytes" }
],
"name": "onERC721Received",
"outputs": [{ "internalType": "bytes4", "name": "", "type": "bytes4" }],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "pauseProfile",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_nftAddress", "type": "address" },
{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }
],
"name": "reactivateProfile",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" },
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" }
],
"name": "removeTeamPoints",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_userAddress", "type": "address" },
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" }
],
"name": "removeUserPoints",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "_userAddresses",
"type": "address[]"
},
{ "internalType": "uint256", "name": "_numberPoints", "type": "uint256" }
],
"name": "removeUserPointsMultiple",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "uint256", "name": "_teamId", "type": "uint256" },
{ "internalType": "string", "name": "_teamName", "type": "string" },
{ "internalType": "string", "name": "_teamDescription", "type": "string" }
],
"name": "renameTeam",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "bytes32", "name": "role", "type": "bytes32" },
{ "internalType": "address", "name": "account", "type": "address" }
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_newNumberCakeToReactivate",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_newNumberCakeToRegister",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_newNumberCakeToUpdate",
"type": "uint256"
}
],
"name": "updateNumberCake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "address", "name": "_nftAddress", "type": "address" },
{ "internalType": "uint256", "name": "_tokenId", "type": "uint256" }
],
"name": "updateProfile",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@ -0,0 +1,71 @@
import { Nft, NftSource, NftType } from "../../types";
import { IPFS_GATEWAY } from "../common";
// This mock file is needed to properly test different NFT types
// and also not rely in tests on any value changes in the future
export const nftSources: NftSource = {
[NftType.PANCAKE]: {
address: {
56: "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07",
97: "0x60935F36e4631F73f0f407e68642144e07aC7f5E",
},
identifierKey: "image",
},
[NftType.MIXIE]: {
address: {
56: "0xa251b5EAa9E67F2Bc8b33F33e20E91552Bf85566",
97: "",
},
identifierKey: "otherIdentifier",
},
};
const Nfts: Nft[] = [
{
name: "Mixie v1",
description: "Stories were told, and songs were sung, about Chef Mixies pancakes and her big Syrup gun.",
images: {
lg: "mixie-1-lg.png",
md: "mixie-1-md.png",
sm: "mixie-1-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmQiRpr7ZMkzV7qbqVaUZ1LiuHTTdpWmapUhaY6ZGmVLQ4/001-Chef-Mixie.png`,
},
sortOrder: 999,
identifier: "001-Chef-Mixie",
type: NftType.MIXIE,
variationId: 1,
},
{
name: "Sleepy",
description: "Aww, looks like eating pancakes all day is tough work. Sweet dreams!",
images: {
lg: "sleepy-lg.png",
md: "sleepy-md.png",
sm: "sleepy-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/sleepy.png`,
blur: "sleepy-blur.png",
},
sortOrder: 999,
identifier: "sleepy",
type: NftType.PANCAKE,
variationId: 5,
},
{
name: "Swapsies",
description: "These bunnies love nothing more than swapping pancakes. Especially on BSC.",
images: {
lg: "swapsies-lg.png",
md: "swapsies-md.png",
sm: "swapsies-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/swapsies.png`,
blur: "swapsies-blur.png",
},
sortOrder: 999,
identifier: "swapsies",
type: NftType.PANCAKE,
variationId: 0,
},
];
export default Nfts;

View File

@ -0,0 +1,169 @@
import { Campaign } from "../types";
/**
* id: The campaign id (required)
* type: The type of the achievement
* title: A string or an object to be translated.
* Note: If the value is a string it is likely used as data in a translation object
*
* badge: Achievement avatar
*/
const campaigns: Campaign[] = [
{
id: "511090000",
type: "ifo",
title: "Horizon Protocol",
badge: "ifo-hzn.svg",
},
{
id: "511080000",
type: "ifo",
title: "Belt",
badge: "ifo-belt.svg",
},
{
id: "511070000",
type: "ifo",
title: "Yieldwatch",
badge: "ifo-watch.svg",
},
{
id: "511060000",
type: "ifo",
title: "Berry",
badge: "ifo-bry.svg",
},
{
id: "511050000",
type: "ifo",
title: "Soteria",
badge: "ifo-wsote.svg",
},
{
id: "511040000",
type: "ifo",
title: "Helmet",
badge: "ifo-helmet.svg",
},
{
id: "511030000",
type: "ifo",
title: "Tenet",
badge: "ifo-ten.svg",
},
{
id: "511020000",
type: "ifo",
title: "Ditto",
badge: "ifo-ditto.svg",
},
{
id: "511010000",
type: "ifo",
title: "Blink",
badge: "ifo-blk.svg",
},
{
id: "512010001",
type: "teambattle",
title: "Easter Champion: Gold",
badge: "easter-champion-gold.svg",
},
{
id: "512010002",
type: "teambattle",
title: "Easter Top 500: Gold",
badge: "easter-top-500-gold.svg",
},
{
id: "512010003",
type: "teambattle",
title: "Easter Top 500: Gold",
badge: "easter-top-500-gold.svg",
},
{
id: "512010004",
type: "teambattle",
title: "Easter Top 500: Gold",
badge: "easter-top-500-gold.svg",
},
{
id: "512010005",
type: "teambattle",
title: "Easter Participant: Gold",
badge: "easter-participant-gold.svg",
},
{
id: "512010006",
type: "teambattle",
title: "Easter Champion: Silver",
badge: "easter-champion-silver.svg",
},
{
id: "512010007",
type: "teambattle",
title: "Easter Top 500: Silver",
badge: "easter-top-500-silver.svg",
},
{
id: "512010008",
type: "teambattle",
title: "Easter Top 500: Silver",
badge: "easter-top-500-silver.svg",
},
{
id: "512010009",
type: "teambattle",
title: "Easter Top 500: Silver",
badge: "easter-top-500-silver.svg",
},
{
id: "512010010",
type: "teambattle",
title: "Easter Participant: Silver",
badge: "easter-participant-silver.svg",
},
{
id: "512010011",
type: "teambattle",
title: "Easter Champion: Bronze",
badge: "easter-champion-bronze.svg",
},
{
id: "512010012",
type: "teambattle",
title: "Easter Top 500: Bronze",
badge: "easter-top-500-bronze.svg",
},
{
id: "512010013",
type: "teambattle",
title: "Easter Top 500: Bronze",
badge: "easter-top-500-bronze.svg",
},
{
id: "512010014",
type: "teambattle",
title: "Easter Top 500: Bronze",
badge: "easter-top-500-bronze.svg",
},
{
id: "512010015",
type: "teambattle",
title: "Easter Participant: Bronze",
badge: "easter-participant-bronze.svg",
},
];
/**
* Transform the campaign config into a map. Keeps the config the same
* as the others and allows easy access to a campaign by id
*/
export const campaignMap = new Map<string, Campaign>();
campaigns.forEach((campaign) => {
campaignMap.set(campaign.id, campaign);
});
export default campaigns;

View File

@ -0,0 +1,7 @@
export const profileApi = "https://profile.pancakeswap.com";
export const profileSubgraphApi = "https://api.thegraph.com/subgraphs/name/pancakeswap/profile";
export const IPFS_GATEWAY = "https://gateway.pinata.cloud";
export const MAINNET_CHAIN_ID = 56;
export const TESTNET_CHAIN_ID = 97;

View File

@ -0,0 +1,6 @@
export default {
pancakeProfile: {
56: "0xDf4dBf6536201370F95e06A0F8a7a70fE40E388a",
97: "0x4B683C7E13B6d5D7fd1FeA9530F451954c1A7c8A",
},
};

View File

@ -0,0 +1,308 @@
import { Nft, NftType, NftSource } from "../types";
import { IPFS_GATEWAY } from "./common";
export const nftSources: NftSource = {
[NftType.PANCAKE]: {
address: {
56: "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07",
97: "0x60935F36e4631F73f0f407e68642144e07aC7f5E",
},
identifierKey: "image",
},
[NftType.MIXIE]: {
address: {
56: "0xa251b5EAa9E67F2Bc8b33F33e20E91552Bf85566",
97: "",
},
identifierKey: "image",
},
};
const Nfts: Nft[] = [
{
name: "Mixie v1",
description: "Stories were told, and songs were sung, about Chef Mixies pancakes and her big Syrup gun.",
images: {
lg: "mixie-1-lg.png",
md: "mixie-1-md.png",
sm: "mixie-1-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmQiRpr7ZMkzV7qbqVaUZ1LiuHTTdpWmapUhaY6ZGmVLQ4/001-Chef-Mixie.png`,
},
sortOrder: 999,
identifier: "001-Chef-Mixie",
type: NftType.MIXIE,
variationId: 1,
},
{
name: "Mixie v2",
description: "Stories were told, and songs were sung, about Chef Mixies pancakes and her big Syrup gun.",
images: {
lg: "mixie-2-lg.png",
md: "mixie-2-md.png",
sm: "mixie-2-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmQiRpr7ZMkzV7qbqVaUZ1LiuHTTdpWmapUhaY6ZGmVLQ4/002-Chef-Mixie.png`,
},
sortOrder: 999,
identifier: "002-Chef-Mixie",
type: NftType.MIXIE,
variationId: 2,
},
{
name: "Mixie v3",
description: "Stories were told, and songs were sung, about Chef Mixies pancakes and her big Syrup gun.",
images: {
lg: "mixie-3-lg.png",
md: "mixie-3-md.png",
sm: "mixie-3-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmQiRpr7ZMkzV7qbqVaUZ1LiuHTTdpWmapUhaY6ZGmVLQ4/003-Chef-Mixie.png`,
},
sortOrder: 999,
identifier: "003-Chef-Mixie",
type: NftType.MIXIE,
variationId: 3,
},
{
name: "Easter 21 Champions",
description: "Eggscellent! Celebrating Syrup Storm winning the Easter Battle!",
images: {
lg: "easter-champion-storm-lg.png",
md: "easter-champion-storm-md.png",
sm: "easter-champion-storm-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmWFQdggxnAkgFNBWixT6v7nrgEnYfYDxG5A9u42aHhU6U/easter-champion-storm.png`,
},
video: {
webm: `${IPFS_GATEWAY}/ipfs/QmWFQdggxnAkgFNBWixT6v7nrgEnYfYDxG5A9u42aHhU6U/easter-champion-storm.webm`,
mp4: `${IPFS_GATEWAY}/ipfs/QmWFQdggxnAkgFNBWixT6v7nrgEnYfYDxG5A9u42aHhU6U/easter-champion-storm.mp4`,
},
sortOrder: 999,
identifier: "easter-champion-storm",
type: NftType.PANCAKE,
variationId: 15,
},
{
name: "Cakeston Easter '21",
description: "Melting Easter eggs and melting hearts!",
images: {
lg: "cakeston-easter-21-lg.png",
md: "cakeston-easter-21-md.png",
sm: "cakeston-easter-21-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmZGqWaovULNEMKxBCGnGjh27JQkAyadS6AW4J4Lzf3XBp/easter-caker.png`,
},
sortOrder: 999,
identifier: "easter-caker",
type: NftType.PANCAKE,
variationId: 15,
},
{
name: "Flipsie Easter '21",
description: "Watch out for Flipsies spatula smash!",
images: {
lg: "flipsie-easter-21-lg.png",
md: "flipsie-easter-21-md.png",
sm: "flipsie-easter-21-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmZGqWaovULNEMKxBCGnGjh27JQkAyadS6AW4J4Lzf3XBp/easter-flipper.png`,
},
sortOrder: 999,
identifier: "easter-flipper",
type: NftType.PANCAKE,
variationId: 14,
},
{
name: "Stormy Easter '21",
description: "Do you like chocolate with your syrup? Go long!",
images: {
lg: "stormy-easter-21-lg.png",
md: "stormy-easter-21-md.png",
sm: "stormy-easter-21-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmZGqWaovULNEMKxBCGnGjh27JQkAyadS6AW4J4Lzf3XBp/easter-storm.png`,
},
sortOrder: 999,
identifier: "easter-storm",
type: NftType.PANCAKE,
variationId: 12,
},
{
name: "Bullish",
description: "Happy Niu Year! This bunnys excited for the year of the bull (market!)",
images: {
lg: "bullish-lg.png",
md: "bullish-md.png",
sm: "bullish-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmNS1A5HsRW1JvFWtGkm4o9TgZVe2P7kA8TB4yxvS6A7ms/bullish.png`,
},
video: {
webm: `${IPFS_GATEWAY}/ipfs/QmNS1A5HsRW1JvFWtGkm4o9TgZVe2P7kA8TB4yxvS6A7ms/bullish.webm`,
mp4: `${IPFS_GATEWAY}/ipfs/QmNS1A5HsRW1JvFWtGkm4o9TgZVe2P7kA8TB4yxvS6A7ms/bullish.mp4`,
},
sortOrder: 999,
identifier: "bullish",
type: NftType.PANCAKE,
variationId: 11,
},
{
name: "Hiccup",
description: "Oopsie daisy! Hiccup's had a bit of an accident. Poor little fella.",
images: {
lg: "hiccup-lg.png",
md: "hiccup-md.png",
sm: "hiccup-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmQ6EE6gkVzAQUdQLLM7CyrnME6LZHCoy92ZERW8HXmyjw/hiccup.png`,
},
sortOrder: 999,
identifier: "hiccup",
type: NftType.PANCAKE,
variationId: 10,
},
{
name: "Sleepy",
description: "Aww, looks like eating pancakes all day is tough work. Sweet dreams!",
images: {
lg: "sleepy-lg.png",
md: "sleepy-md.png",
sm: "sleepy-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/sleepy.png`,
blur: "sleepy-blur.png",
},
sortOrder: 999,
identifier: "sleepy",
type: NftType.PANCAKE,
variationId: 5,
},
{
name: "Sunny",
description: "Sunny is always cheerful when there are pancakes around. Smile!",
images: {
lg: "sunny-lg.png",
md: "sunny-md.png",
sm: "sunny-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/sunny.png`,
blur: "sunny-blur.png",
},
sortOrder: 999,
identifier: "sunny",
type: NftType.PANCAKE,
variationId: 9,
},
{
name: "Churro",
description: "Don't let that dopey smile deceive you... Churro's a master CAKE chef!",
images: {
lg: "churro-lg.png",
md: "churro-md.png",
sm: "churro-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/churro.png`,
blur: "churro-blur.png",
},
sortOrder: 999,
identifier: "churro",
type: NftType.PANCAKE,
variationId: 8,
},
{
name: "Dollop",
description: "Nommm... Oh hi, I'm just meditating on the meaning of CAKE.",
images: {
lg: "dollop-lg.png",
md: "dollop-md.png",
sm: "dollop-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/dollop.png`,
blur: "dollop-blur.png",
},
sortOrder: 999,
identifier: "dollop",
type: NftType.PANCAKE,
variationId: 6,
},
{
name: "Twinkle",
description: "Three guesses what's put that twinkle in those eyes! (Hint: it's CAKE)",
images: {
lg: "twinkle-lg.png",
md: "twinkle-md.png",
sm: "twinkle-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/twinkle.png`,
blur: "twinkle-blur.png",
},
sortOrder: 999,
identifier: "twinkle",
type: NftType.PANCAKE,
variationId: 7,
},
{
name: "Swapsies",
description: "These bunnies love nothing more than swapping pancakes. Especially on BSC.",
images: {
lg: "swapsies-lg.png",
md: "swapsies-md.png",
sm: "swapsies-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/swapsies.png`,
blur: "swapsies-blur.png",
},
sortOrder: 999,
identifier: "swapsies",
type: NftType.PANCAKE,
variationId: 0,
},
{
name: "Drizzle",
description: "It's raining syrup on this bunny, but he doesn't seem to mind. Can you blame him?",
images: {
lg: "drizzle-lg.png",
md: "drizzle-md.png",
sm: "drizzle-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/drizzle.png`,
blur: "drizzle-blur.png",
},
sortOrder: 999,
identifier: "drizzle",
type: NftType.PANCAKE,
variationId: 1,
},
{
name: "Blueberries",
description: "These bunnies like their pancakes with blueberries. What's your favorite topping?",
images: {
lg: "blueberries-lg.png",
md: "blueberries-md.png",
sm: "blueberries-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/blueberries.png`,
blur: "blueberries-blur.png",
},
sortOrder: 999,
identifier: "blueberries",
type: NftType.PANCAKE,
variationId: 2,
},
{
name: "Circular",
description: "Love makes the world go 'round... but so do pancakes. And these bunnies know it.",
images: {
lg: "circular-lg.png",
md: "circular-md.png",
sm: "circular-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/circular.png`,
blur: "circular-blur.png",
},
sortOrder: 999,
identifier: "circular",
type: NftType.PANCAKE,
variationId: 3,
},
{
name: "Sparkle",
description: "Its sparkling syrup, pancakes, and even lottery tickets! This bunny really loves it.",
images: {
lg: "sparkle-lg.png",
md: "sparkle-md.png",
sm: "sparkle-sm.png",
ipfs: `${IPFS_GATEWAY}/ipfs/QmXdHqg3nywpNJWDevJQPtkz93vpfoHcZWQovFz2nmtPf5/sparkle.png`,
blur: "sparkle-blur.png",
},
sortOrder: 999,
identifier: "sparkle",
type: NftType.PANCAKE,
variationId: 4,
},
];
export default Nfts;

View File

@ -0,0 +1,54 @@
import { Team } from "../types";
const teams: Team[] = [
{
id: 1,
name: "Syrup Storm",
description: "The storm's a-comin! Watch out! These bulls are stampeding in a syrupy surge!",
images: {
lg: "syrup-storm-lg.png",
md: "syrup-storm-md.png",
sm: "syrup-storm-sm.png",
alt: "syrup-storm-alt.png",
ipfs: "https://gateway.pinata.cloud/ipfs/QmXKzSojwzYjtDCVgR6mVx7w7DbyYpS7zip4ovJB9fQdMG/syrup-storm.png",
},
background: "syrup-storm-bg.svg",
textColor: "#191326",
users: 0,
points: 0,
},
{
id: 2,
name: "Fearsome Flippers",
description: "The flippening is coming. Don't get in these bunnies' way, or you'll get flipped too!",
images: {
lg: "fearsome-flippers-lg.png",
md: "fearsome-flippers-md.png",
sm: "fearsome-flippers-sm.png",
alt: "fearsome-flippers-alt.png",
ipfs: "https://gateway.pinata.cloud/ipfs/QmXKzSojwzYjtDCVgR6mVx7w7DbyYpS7zip4ovJB9fQdMG/fearsome-flippers.png",
},
background: "fearsome-flippers-bg.svg",
textColor: "#FFFFFF",
users: 0,
points: 0,
},
{
id: 3,
name: "Chaotic Cakers",
description: "Can you stand the heat? Stay out of the kitchen or you might get burned to a crisp!",
images: {
lg: "chaotic-cakers-lg.png",
md: "chaotic-cakers-md.png",
sm: "chaotic-cakers-sm.png",
alt: "chaotic-cakers-alt.png",
ipfs: "https://gateway.pinata.cloud/ipfs/QmXKzSojwzYjtDCVgR6mVx7w7DbyYpS7zip4ovJB9fQdMG/chaotic-cakers.png",
},
background: "chaotic-cakers-bg.svg",
textColor: "#191326",
users: 0,
points: 0,
},
];
export default teams;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1152" height="236" fill="none" viewBox="0 0 1152 236"><g clip-path="url(#clip0)"><rect width="1152" height="236" fill="url(#paint0_linear)"/><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" fill-rule="evenodd" d="M874.964 340.897C879.176 348.121 888.651 350.208 895.503 345.421L1145.88 170.491C1151.99 166.222 1153.8 157.972 1150.05 151.53L1034.94 -45.8969C1031.18 -52.339 1023.11 -54.8155 1016.4 -51.5885L741.12 80.6395C733.586 84.2582 730.742 93.5437 734.954 100.768L874.964 340.897ZM789.743 124.776C782.727 112.742 787.142 97.3544 799.395 91.1399L1005.66 -13.4706C1015.34 -18.3788 1027.22 -14.7894 1032.77 -5.2844C1038.67 4.83554 1035.16 17.7709 1025.01 23.3004L821.948 133.99C810.608 140.172 796.34 136.09 789.743 124.776ZM870.692 211.192C858.276 218.719 842.03 214.467 834.615 201.75C827.2 189.033 831.491 172.78 844.144 165.66L1042.02 54.2948C1052.66 48.3049 1066.2 52.0818 1072.44 62.7792C1078.68 73.4767 1075.3 87.139 1064.86 93.4707L870.692 211.192ZM913.505 286.849C902.074 294.474 886.519 290.756 879.502 278.721C872.906 267.408 876.372 252.964 887.325 246.118L1083.46 123.54C1093.26 117.417 1106.23 120.72 1112.13 130.84C1117.68 140.345 1114.95 152.47 1105.93 158.493L913.505 286.849Z" clip-rule="evenodd"/></g><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" fill-rule="evenodd" d="M329.931 -126.551C324.914 -135.144 313.63 -137.627 305.47 -131.932L7.28734 76.1521C0.0104405 81.2302 -2.14884 91.0436 2.32452 98.7067L139.415 333.551C143.888 341.214 153.496 344.16 161.496 340.321L489.33 183.032C498.302 178.728 501.689 167.682 496.673 159.089L329.931 -126.551ZM431.422 130.531C439.779 144.846 434.521 163.149 419.928 170.542L174.28 294.979C162.754 300.817 148.6 296.547 142 285.241C134.973 273.203 139.15 257.816 151.231 251.239L393.069 119.571C406.575 112.217 423.566 117.073 431.422 130.531ZM335.018 27.7369C349.804 18.7829 369.152 23.8407 377.983 38.9683C386.814 54.0959 381.704 73.4283 366.635 81.8987L130.98 214.37C118.305 221.495 102.176 217.003 94.7479 204.278C87.3197 191.553 91.3387 175.301 103.776 167.769L335.018 27.7369ZM284.031 -62.2589C297.645 -71.3292 316.169 -66.9067 324.526 -52.5913C332.382 -39.1336 328.254 -21.9516 315.21 -13.8086L81.6272 132.001C69.9586 139.285 54.5052 135.355 47.478 123.317C40.8779 112.011 44.1201 97.5876 54.8721 90.4238L284.031 -62.2589Z" clip-rule="evenodd"/></g></g><defs><linearGradient id="paint0_linear" x1="576" x2="576" y1="0" y2="236" gradientUnits="userSpaceOnUse"><stop stop-color="#483490"/><stop offset="1" stop-color="#6352D3"/></linearGradient><clipPath id="clip0"><rect width="1152" height="236" fill="#fff"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,2 @@
declare module "*.png";
declare module "*.svg";

View File

@ -0,0 +1,17 @@
export { default as chaoticCakersAlt } from "./chaotic-cakers-alt.png";
export { default as chaoticCakersSm } from "./chaotic-cakers-sm.png";
export { default as chaoticCakersMd } from "./chaotic-cakers-md.png";
export { default as chaoticCakersLg } from "./chaotic-cakers-lg.png";
export { default as chaoticCakersBg } from "./chaotic-cakers-bg.svg";
export { default as fearsomeFlippersAlt } from "./fearsome-flippers-alt.png";
export { default as fearsomeFlippersSm } from "./fearsome-flippers-sm.png";
export { default as fearsomeFlippersMd } from "./fearsome-flippers-md.png";
export { default as fearsomeFlippersLg } from "./fearsome-flippers-lg.png";
export { default as fearsomeFlippersBg } from "./fearsome-flippers-bg.svg";
export { default as syrupStormAlt } from "./syrup-storm-alt.png";
export { default as syrupStormSm } from "./syrup-storm-sm.png";
export { default as syrupStormMd } from "./syrup-storm-md.png";
export { default as syrupStormLg } from "./syrup-storm-lg.png";
export { default as syrupStormBg } from "./syrup-storm-bg.svg";

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1152" height="236" fill="none" viewBox="0 0 1152 236"><g clip-path="url(#clip0)"><rect width="1152" height="236" fill="url(#paint0_linear)"/><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" d="M0 77.1653C0 77.1653 43.1392 32.398 132.443 -91.7735L340 -157L227.798 -32.2232C249.793 -35.9862 275.29 -40.8428 304.017 -47.0962C304.017 -47.0962 131.52 137.963 30.3773 228C57.9075 153.78 83.4395 91.1969 101.933 47.5071C77.1653 54.7431 44.1092 64.3677 0 77.1653Z"/></g><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" d="M588 77.1653C588 77.1653 631.139 32.398 720.443 -91.7735L928 -157L815.798 -32.2232C837.793 -35.9862 863.29 -40.8428 892.017 -47.0962C892.017 -47.0962 719.52 137.963 618.377 228C645.908 153.78 671.439 91.1969 689.933 47.5071C665.165 54.7431 632.109 64.3677 588 77.1653Z"/></g><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" d="M169 241.267C169 241.267 219.118 189.407 322.868 45.5611L564 -30L433.648 114.547C459.201 110.187 488.822 104.561 522.196 97.3171C522.196 97.3171 321.796 311.697 204.291 416C236.275 330.02 265.937 257.522 287.422 206.91C258.648 215.292 220.244 226.442 169 241.267Z"/></g><g style="mix-blend-mode:soft-light" opacity=".2"><path fill="#fff" d="M758 241.267C758 241.267 807.991 189.407 911.479 45.5611L1152 -30L1021.98 114.547C1047.47 110.187 1077.01 104.561 1110.3 97.3171C1110.3 97.3171 910.409 311.697 793.202 416C825.105 330.02 854.692 257.522 876.122 206.91C847.421 215.292 809.115 226.442 758 241.267Z"/></g></g><defs><linearGradient id="paint0_linear" x1="576" x2="576" y1="0" y2="236" gradientUnits="userSpaceOnUse"><stop stop-color="#53DEE9"/><stop offset="1" stop-color="#1FC7D4"/></linearGradient><clipPath id="clip0"><rect width="1152" height="236" fill="#fff"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,5 @@
import PancakeProfileSdk from "./profile-sdk";
export * from "./images";
export * from "./types";
export default PancakeProfileSdk;

View File

@ -0,0 +1,77 @@
import { rest, graphql } from "msw";
import { existingAddress1, existingAddress2, nonexistentAddress } from "./mockAddresses";
import { profileApi, profileSubgraphApi, IPFS_GATEWAY } from "../constants/common";
const subgraph = graphql.link(profileSubgraphApi);
const handlers = [
rest.get(`${profileApi}/api/users/${existingAddress1}`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
username: "Cheems",
})
);
}),
rest.get(`${profileApi}/api/users/${nonexistentAddress}`, (req, res, ctx) => {
return res(ctx.status(404), ctx.json({ error: { message: "Entity not found." } }));
}),
rest.get(`${IPFS_GATEWAY}/ipfs/QmYsTqbmGA3H5cgouCkh8tswJAQE1AsEko9uBZX9jZ3oTC/sleepy.json`, (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
name: "Sleepy",
description: "Aww, looks like eating pancakes all day is tough work. Sweet dreams!",
image: "ipfs://QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/sleepy.png",
attributes: {
bunnyId: "5",
},
})
);
}),
subgraph.query("getUser", (req, res, ctx) => {
const address = req.variables.id;
if (address === existingAddress1) {
return res(
ctx.data({
user: {
points: [
{
id: existingAddress1,
campaignId: "511080000",
points: 200,
},
{
id: existingAddress1,
campaignId: "512010010",
points: 500,
},
{
id: existingAddress1,
campaignId: "511090000",
points: 100,
},
],
},
})
);
}
if (address === existingAddress2) {
return res(
ctx.data({
user: {
points: [],
},
})
);
}
// Address does not exists
return res(
ctx.data({
user: null,
})
);
}),
];
export default handlers;

View File

@ -0,0 +1,3 @@
export const existingAddress1 = "0x1111111111111111111111111111111111111111";
export const existingAddress2 = "0x2222222222222222222222222222222222222222";
export const nonexistentAddress = "0x9999999999999999999999999999999999999999";

View File

@ -0,0 +1,9 @@
import { rest } from "msw";
import { setupServer } from "msw/node";
import handlers from "./handlers";
// This configures a request mocking server with the given request handlers.
const server = setupServer(...handlers);
// Use these to set up special cases during tests
// No need for manual teardown, all runtime modifications are cleaned up in jest.setup.js afterEach
export { server, rest };

View File

@ -0,0 +1,187 @@
import Web3 from "web3";
import Cookies from "js-cookie";
import PancakeProfileSdk from "./profile-sdk";
import web3NoAccount from "./utils/web3";
import { MAINNET_CHAIN_ID, TESTNET_CHAIN_ID, profileApi } from "./constants/common";
import nfts from "./constants/nfts";
import teamsList from "./constants/teams";
import { existingAddress1, existingAddress2, nonexistentAddress } from "./mocks/mockAddresses";
import { server, rest } from "./mocks/server";
jest.mock("./utils/contractHelpers");
jest.mock("js-cookie", () => ({
set: jest.fn(() => null),
}));
describe("PancakeProfileSdk", () => {
describe("constructor", () => {
it("uses default web3 instance if no web3 is provided", () => {
const sdk = new PancakeProfileSdk();
expect(sdk.web3).toBe(web3NoAccount);
});
it("uses mainnet chainId if no chainId is provided", () => {
const sdk = new PancakeProfileSdk();
expect(sdk.chainId).toBe(MAINNET_CHAIN_ID);
});
it("uses custom web3 instance if provided", () => {
const httpProvider = new Web3.providers.HttpProvider("http://customrpc.com", {
timeout: 10000,
});
const customWeb3 = new Web3(httpProvider);
const sdk = new PancakeProfileSdk({ web3: customWeb3 });
expect(sdk.web3).toBe(customWeb3);
});
it("uses specific chainId if chainId is provided", () => {
const sdk = new PancakeProfileSdk({ chainId: TESTNET_CHAIN_ID });
expect(sdk.chainId).toBe(TESTNET_CHAIN_ID);
});
});
describe("methods", () => {
const sdk = new PancakeProfileSdk();
beforeEach(() => {
jest.clearAllMocks();
});
describe("getUsername", () => {
it("returns username for valid address", async () => {
await expect(sdk.getUsername(existingAddress1)).resolves.toEqual("Cheems");
});
it("returns empty string for invalid address", async () => {
await expect(sdk.getUsername(nonexistentAddress)).resolves.toEqual("");
});
it("returns empty string when there is internal server error", async () => {
server.use(
rest.get(`${profileApi}/api/users/${existingAddress1}`, async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ message: "500 Internal Server Error" }));
})
);
await expect(sdk.getUsername(nonexistentAddress)).resolves.toEqual("");
});
});
describe("getAchievements", () => {
it("returns achievements for existing address", async () => {
const expectedAchievements = [
{
id: "511080000",
type: "ifo",
address: existingAddress1,
title: {
id: 999,
fallback: `IFO Shopper: Belt`,
data: {
name: "Belt",
},
},
description: {
id: 999,
fallback: `Committed more than $5 worth of LP in the Belt IFO`,
data: {
name: "Belt",
},
},
badge: "ifo-belt.svg",
points: 200,
},
{
id: "512010010",
type: "teambattle",
address: existingAddress1,
title: "Easter Participant: Silver",
badge: "easter-participant-silver.svg",
points: 500,
},
{
id: "511090000",
type: "ifo",
address: existingAddress1,
title: {
id: 999,
fallback: `IFO Shopper: Horizon Protocol`,
data: {
name: "Horizon Protocol",
},
},
description: {
id: 999,
fallback: `Committed more than $5 worth of LP in the Horizon Protocol IFO`,
data: {
name: "Horizon Protocol",
},
},
badge: "ifo-hzn.svg",
points: 100,
},
];
const achievements = await sdk.getAchievements(existingAddress1);
expect(achievements).toEqual(expectedAchievements);
});
it("returns empty array for address with no achievements", async () => {
const achievements = await sdk.getAchievements(existingAddress2);
expect(achievements).toEqual([]);
});
it("returns empty array for non-existent address", async () => {
const achievements = await sdk.getAchievements(nonexistentAddress);
expect(achievements).toEqual([]);
});
});
describe("getTeam", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("returns team data for valid team", async () => {
await expect(sdk.getTeam(2)).resolves.toEqual({
...teamsList[1],
users: 77000,
points: 341500,
isJoinable: true,
});
});
it("returns null for non-existent team id", async () => {
await expect(sdk.getTeam(69)).resolves.toEqual(null);
});
});
describe("getProfile", () => {
const sleepyNft = nfts.find((nft) => nft.identifier === "sleepy");
beforeEach(() => {
jest.clearAllMocks();
});
it("returns proper response for unregistered user", async () => {
const profile = await sdk.getProfile(nonexistentAddress);
expect(profile).toEqual({ hasRegistered: false, profile: null });
});
it("returns proper response for registered user", async () => {
const profile = await sdk.getProfile(existingAddress1);
expect(profile).toEqual({
hasRegistered: true,
profile: {
isActive: true,
userId: 123,
username: "Cheems",
teamId: 2,
points: 3000,
tokenId: 555,
nftAddress: "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07",
nft: sleepyNft,
team: { ...teamsList[1], users: 77000, points: 341500, isJoinable: true },
},
});
});
it("sets cookies", async () => {
await sdk.getProfile(existingAddress1);
expect(Cookies.set).toBeCalledWith(
`profile_${existingAddress1}`,
{
username: "Cheems",
avatar: `https://pancakeswap.finance/images/nfts/${sleepyNft.images.sm}`,
},
{ domain: "pancakeswap.finance", secure: true, expires: 30 }
);
expect(Cookies.set).toBeCalledTimes(1);
});
});
});
});

View File

@ -0,0 +1,172 @@
import Web3 from "web3";
import { Contract } from "web3-eth-contract";
import { request, gql } from "graphql-request";
import Cookies from "js-cookie";
import web3NoAccount from "./utils/web3";
import { getProfileContract } from "./utils/contractHelpers";
import { profileApi, profileSubgraphApi, MAINNET_CHAIN_ID } from "./constants/common";
import { campaignMap } from "./constants/campaigns";
import teamsList from "./constants/teams";
import { Achievement, Team, GetProfileResponse, Profile, Nft, UserPointIncreaseEvent } from "./types";
import { getAchievementDescription, getAchievementTitle, transformProfileResponse } from "./utils/transformHelpers";
import { getNftByTokenId } from "./utils/collectibles";
type SdkConstructorArguments = {
web3?: Web3;
chainId?: number;
};
class PancakeProfileSdk {
web3 = web3NoAccount;
chainId = MAINNET_CHAIN_ID;
profileContract: Contract;
constructor(args?: SdkConstructorArguments) {
if (args?.web3) this.web3 = args.web3;
if (args?.chainId) this.chainId = args.chainId;
this.profileContract = getProfileContract(this.web3, this.chainId);
}
/**
* Fetches user information via REST API
* Contains user information and leaderboard statistics about latest trading competition.
* API repo - https://github.com/pancakeswap/pancake-profile-api
*/
getUsername = async (address: string): Promise<string> => {
try {
const response = await fetch(`${profileApi}/api/users/${address}`);
if (!response.ok) {
return "";
}
const { username = "" } = await response.json();
return username;
} catch (error) {
return "";
}
};
getAchievements = async (account: string): Promise<Achievement[]> => {
try {
const data = await request(
profileSubgraphApi,
gql`
query getUser($id: String!) {
user(id: $id) {
points {
id
campaignId
points
}
}
}
`,
{ id: account.toLowerCase() }
);
if (data.user === null || data.user.points.length === 0) {
return [];
}
return data.user.points.reduce((accum: Achievement[], userPoint: UserPointIncreaseEvent) => {
const campaignMeta = campaignMap.get(userPoint.campaignId);
return [
...accum,
{
id: userPoint.campaignId,
type: campaignMeta.type,
address: userPoint.id,
title: getAchievementTitle(campaignMeta),
description: getAchievementDescription(campaignMeta),
badge: campaignMeta.badge,
points: Number(userPoint.points),
},
];
}, []);
} catch (error) {
return [];
}
};
/**
* Fetches team information from
* Contains team name, number of users, total number of points for the team and whether the team is joinable.
* This data is combined with static team data (images, description, etc) that is stored in constant in this repo.
* Contract repo - https://github.com/pancakeswap/pancake-contracts/tree/master/projects/profile-nft-gamification
*/
getTeam = async (teamId: number): Promise<Team> => {
try {
const {
0: teamName,
2: numberUsers,
3: numberPoints,
4: isJoinable,
} = await this.profileContract.methods.getTeamProfile(teamId).call();
const staticTeamInfo = teamsList.find((staticTeam) => staticTeam.id === teamId);
return { ...staticTeamInfo, isJoinable, name: teamName, users: numberUsers, points: numberPoints };
} catch (error) {
return null;
}
};
/**
* Fetches profile information for specified address.
* This function combines data from getUsername and getTeam with profile data received getUserProfile method
* from PancakeProfile contract.
* NFT's bunnyId is retrieved from PancakeBunnies contract and mapped to static NFT data stored in constant.
* Contracts repo - https://github.com/pancakeswap/pancake-contracts/tree/master/projects/profile-nft-gamification
*/
getProfile = async (address: string): Promise<GetProfileResponse> => {
try {
const hasRegistered = (await this.profileContract.methods.hasRegistered(address).call()) as boolean;
if (!hasRegistered) {
return { hasRegistered, profile: null };
}
const profileResponse = await this.profileContract.methods.getUserProfile(address).call();
const { userId, points, teamId, tokenId, nftAddress, isActive } = transformProfileResponse(profileResponse);
const team = await this.getTeam(teamId);
const username = await this.getUsername(address);
// If the profile is not active the tokenId returns 0, which is still a valid token id
// so only fetch the nft data if active
let nft: Nft;
if (isActive) {
nft = await getNftByTokenId(nftAddress, tokenId, this.web3, this.chainId);
const avatar = nft ? `https://pancakeswap.finance/images/nfts/${nft.images.sm}` : undefined;
// Save the preview image in a cookie so it can be used on the exchange
// TODO v2: optional (and configurable) Cookies.set
Cookies.set(
`profile_${address}`,
{
username,
avatar,
},
{ domain: "pancakeswap.finance", secure: true, expires: 30 }
);
}
const profile = {
userId,
points,
teamId,
tokenId,
username,
nftAddress,
isActive,
nft,
team,
} as Profile;
return { hasRegistered, profile };
} catch (error) {
console.error("getProfile error: ", error);
return null;
}
};
}
export default PancakeProfileSdk;

View File

@ -0,0 +1,127 @@
export interface Address {
97?: string;
56: string;
}
export type Images = {
lg: string;
md: string;
sm: string;
ipfs?: string;
};
export type NftImages = {
blur?: string;
} & Images;
export type NftVideo = {
webm: string;
mp4: string;
};
export type Nft = {
name: string;
description: string;
images: NftImages;
sortOrder: number;
type: NftType;
video?: NftVideo;
// Uniquely identifies the nft.
// Used for matching an NFT from the config with the data from the NFT's tokenURI
identifier: string;
// Used to be "bunnyId". Used when minting NFT
variationId?: number | string;
};
export enum NftType {
PANCAKE = "pancake",
MIXIE = "mixie",
}
export type NftUriData = {
name: string;
description: string;
image: string;
attributes: {
bunnyId: string;
};
};
export type NftSource = {
[key in NftType]: {
address: Address;
identifierKey: string;
};
};
export type TeamImages = {
alt: string;
} & Images;
export type Team = {
id: number;
name: string;
description: string;
isJoinable?: boolean;
users: number;
points: number;
images: TeamImages;
background: string;
textColor: string;
};
export interface GetProfileResponse {
hasRegistered: boolean;
profile?: Profile;
}
export interface Profile {
userId: number;
points: number;
teamId: number;
nftAddress: string;
tokenId: number;
isActive: boolean;
username: string;
nft?: Nft;
team: Team;
hasRegistered: boolean;
}
export type TranslatableText =
| string
| {
id: number;
fallback: string;
data?: {
[key: string]: string | number;
};
};
export type CampaignType = "ifo" | "teambattle";
export type Campaign = {
id: string;
type: CampaignType;
title?: TranslatableText;
description?: TranslatableText;
badge?: string;
};
export interface Achievement {
id: string;
type: CampaignType;
address: string;
title: TranslatableText;
description?: TranslatableText;
badge: string;
points: number;
}
export interface UserPointIncreaseEvent {
campaignId: string;
id: string; // wallet address
points: string;
}

View File

@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import Web3 from "web3";
import { existingAddress1 } from "../../mocks/mockAddresses";
const getProfileContract = jest.fn((web3?: Web3) => {
return {
methods: {
hasRegistered: jest.fn((callAddress: string) => {
if (callAddress === existingAddress1) {
return { call: jest.fn(() => Promise.resolve(true)) };
}
return { call: jest.fn(() => Promise.resolve(false)) };
}),
getUserProfile: jest.fn((callAddress: string) => {
if (callAddress === existingAddress1) {
return {
call: jest.fn(() =>
Promise.resolve({
0: 123,
1: 3000,
2: 2,
3: "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07",
4: 555,
5: true,
})
),
};
}
return { call: jest.fn(() => Promise.resolve(null)) };
}),
getTeamProfile: jest.fn((teamId: number) => {
if (teamId === 2) {
return {
call: jest.fn(() =>
Promise.resolve({
0: "Fearsome Flippers",
2: 77000,
3: 341500,
4: true,
})
),
};
}
return { call: jest.fn(() => Promise.resolve(null)) };
}),
},
};
});
const getErc721Contract = jest.fn((web3?: Web3) => {
return {
methods: {
tokenURI: jest.fn((tokenId: string) => {
return {
call: jest.fn(() => Promise.resolve("ipfs://QmYsTqbmGA3H5cgouCkh8tswJAQE1AsEko9uBZX9jZ3oTC/sleepy.json")),
};
}),
},
};
});
module.exports = { getProfileContract, getErc721Contract };

View File

@ -0,0 +1,14 @@
import addresses from "../constants/contracts";
import { MAINNET_CHAIN_ID, TESTNET_CHAIN_ID } from "../constants/common";
import { getPancakeProfileAddress } from "./addressHelpers";
describe("addressHelpers", () => {
it("getAddress returns correct mainnet address", () => {
const profileAddress = getPancakeProfileAddress(MAINNET_CHAIN_ID);
expect(profileAddress).toBe(addresses.pancakeProfile[MAINNET_CHAIN_ID]);
});
it("getAddress returns correct testnet address", () => {
const profileAddress = getPancakeProfileAddress(TESTNET_CHAIN_ID);
expect(profileAddress).toBe(addresses.pancakeProfile[TESTNET_CHAIN_ID]);
});
});

View File

@ -0,0 +1,10 @@
import addresses from "../constants/contracts";
import { Address } from "../types";
export const getNftAddress = (nftAddresses: Address, chainId: number): string => {
return nftAddresses[chainId];
};
export const getPancakeProfileAddress = (chainId: number): string => {
return addresses.pancakeProfile[chainId];
};

View File

@ -0,0 +1,94 @@
import { MAINNET_CHAIN_ID, IPFS_GATEWAY } from "../constants/common";
import web3NoAccount from "./web3";
import { getIdentifierKeyFromAddress, getTokenUrl, getTokenUriData, getNftByTokenId } from "./collectibles";
import nfts from "../constants/nfts";
import { server, rest } from "../mocks/server";
jest.mock("../constants/nfts");
jest.mock("./contractHelpers");
const PANCAKE_NFT_ADDRESS = "0xDf7952B35f24aCF7fC0487D01c8d5690a60DBa07";
const MIXIE_NFT_ADDRESS = "0xa251b5EAa9E67F2Bc8b33F33e20E91552Bf85566";
const UNKNOWN_NFT_ADDRESS = "0xa111122229E67F2Bc8b33F33e20E915522221111";
const MOCK_TOKEN_ID = 5;
describe("collectibles", () => {
it("getIdentifierKeyFromAddress returns proper identifier key", () => {
const pancakeIdentifierKey = getIdentifierKeyFromAddress(PANCAKE_NFT_ADDRESS, MAINNET_CHAIN_ID);
const mixieIdentifierKey = getIdentifierKeyFromAddress(MIXIE_NFT_ADDRESS, MAINNET_CHAIN_ID);
expect(pancakeIdentifierKey).toBe("image");
expect(mixieIdentifierKey).toBe("otherIdentifier");
});
it("getIdentifierKeyFromAddress returns null for unknown nft", () => {
const identifierKey = getIdentifierKeyFromAddress(UNKNOWN_NFT_ADDRESS, MAINNET_CHAIN_ID);
expect(identifierKey).toBeNull();
});
it("getTokenUrl returns ipfs link if tokenUri is ipfs uri", () => {
const originalUri = "ipfs://example/something.json";
const tokenUri = getTokenUrl(originalUri);
expect(tokenUri).toBe(`${IPFS_GATEWAY}/ipfs/${originalUri.slice(7)}`);
});
it("getTokenUrl returns https link if tokenUri is https uri", () => {
const originalUri = "https://example.com/something.json";
const tokenUri = getTokenUrl(originalUri);
expect(tokenUri).toBe(originalUri);
});
it("getTokenUriData returns proper response", async () => {
const uriData = await getTokenUriData(PANCAKE_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount);
expect(uriData).toEqual({
name: "Sleepy",
description: "Aww, looks like eating pancakes all day is tough work. Sweet dreams!",
image: "ipfs://QmYD9AtzyQPjSa9jfZcZq88gSaRssdhGmKqQifUDjGFfXm/sleepy.png",
attributes: {
bunnyId: "5",
},
});
});
it("getTokenUriData returns null if request failed", async () => {
server.use(
rest.get(
`${IPFS_GATEWAY}/ipfs/QmYsTqbmGA3H5cgouCkh8tswJAQE1AsEko9uBZX9jZ3oTC/sleepy.json`,
async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ message: "500 Internal Server Error" }));
}
)
);
const uriData = await getTokenUriData(PANCAKE_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount);
expect(uriData).toBeNull();
});
it("getNftByTokenId returns proper nft", async () => {
const sleepyNft = nfts.find((nft) => nft.identifier === "sleepy");
const nft = await getNftByTokenId(PANCAKE_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount, MAINNET_CHAIN_ID);
expect(nft).toBe(sleepyNft);
});
it("getNftByTokenId returns null if uriData is null", async () => {
server.use(
rest.get(
`${IPFS_GATEWAY}/ipfs/QmYsTqbmGA3H5cgouCkh8tswJAQE1AsEko9uBZX9jZ3oTC/sleepy.json`,
async (req, res, ctx) => {
return res(ctx.status(500), ctx.json({ message: "500 Internal Server Error" }));
}
)
);
const nft = await getNftByTokenId(PANCAKE_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount, MAINNET_CHAIN_ID);
expect(nft).toBe(null);
});
it("getNftByTokenId returns null if identifierKey is null", async () => {
const nft = await getNftByTokenId(UNKNOWN_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount, MAINNET_CHAIN_ID);
expect(nft).toBe(null);
});
it("getNftByTokenId returns null if uriData does not contain indentifierKey", async () => {
// In the NFT constant mocks MIXIE NFT type is intentionally given wrong identifier
const nft = await getNftByTokenId(MIXIE_NFT_ADDRESS, MOCK_TOKEN_ID, web3NoAccount, MAINNET_CHAIN_ID);
expect(nft).toBe(null);
});
});

View File

@ -0,0 +1,76 @@
import Web3 from "web3";
import Nfts, { nftSources } from "../constants/nfts";
import { IPFS_GATEWAY } from "../constants/common";
import { Nft, NftUriData } from "../types";
import { getNftAddress } from "./addressHelpers";
import { getErc721Contract } from "./contractHelpers";
/**
* Gets the identifier key based on the nft address
* Helpful for looking up the key when all you have is the address
*/
export const getIdentifierKeyFromAddress = (nftAddress: string, chainId: number): string | null => {
const nftSource = Object.values(nftSources).find((nftSourceEntry) => {
const address = getNftAddress(nftSourceEntry.address, chainId);
return address === nftAddress;
});
return nftSource ? nftSource.identifierKey : null;
};
/**
* Some sources like Pancake do not return HTTP tokenURI's
*/
export const getTokenUrl = (tokenUri: string): string => {
if (tokenUri.startsWith("ipfs://")) {
return `${IPFS_GATEWAY}/ipfs/${tokenUri.slice(7)}`;
}
return tokenUri;
};
export const getTokenUriData = async (nftAddress: string, tokenId: number, web3: Web3): Promise<NftUriData | null> => {
try {
const contract = getErc721Contract(nftAddress, web3);
const tokenUri = await contract.methods.tokenURI(tokenId).call();
const uriDataResponse = await fetch(getTokenUrl(tokenUri));
if (!uriDataResponse.ok) {
return null;
}
const uriData: NftUriData = await uriDataResponse.json();
return uriData;
} catch (error) {
console.error("getTokenUriData", error);
return null;
}
};
export const getNftByTokenId = async (
nftAddress: string,
tokenId: number,
web3: Web3,
chainId: number
): Promise<Nft | null> => {
const uriData = await getTokenUriData(nftAddress, tokenId, web3);
const identifierKey = getIdentifierKeyFromAddress(nftAddress, chainId);
// Bail out early if we have no uriData, identifierKey, or the value does not
// exist in the object
if (!uriData) {
return null;
}
if (!identifierKey) {
return null;
}
if (!uriData[identifierKey]) {
return null;
}
return Nfts.find((nft) => {
return uriData[identifierKey].includes(nft.identifier);
});
};

View File

@ -0,0 +1,26 @@
import Web3 from "web3";
import { MAINNET_CHAIN_ID } from "../constants/common";
import web3NoAccount from "./web3";
import { getProfileContract, getErc721Contract } from "./contractHelpers";
describe("contractHelpers", () => {
it("getProfileContract returns an instance of Contract", () => {
const profileContract = getProfileContract(web3NoAccount, MAINNET_CHAIN_ID);
// toBeInstanceOf doesn't work very well with third-party libs, read more - https://stackoverflow.com/a/58032069/4614082
expect(profileContract.constructor.name).toBe("Contract");
});
it("getErc721Contract returns an instance of Contract", () => {
const erc721Contract = getErc721Contract("0x7777777777777777777777777777777777777777", web3NoAccount);
expect(erc721Contract.constructor.name).toBe("Contract");
});
it("uses provided Web3 instnace", () => {
const httpProvider = new Web3.providers.HttpProvider("https://example.com", {
timeout: 10000,
});
const customWeb3 = new Web3(httpProvider);
const pancakeRabbitContract = getProfileContract(customWeb3, MAINNET_CHAIN_ID);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(pancakeRabbitContract.currentProvider).toBe(httpProvider);
});
});

View File

@ -0,0 +1,18 @@
import Web3 from "web3";
import { AbiItem } from "web3-utils";
import { Contract } from "web3-eth-contract";
import erc721Abi from "../abi/erc721.json";
import profileABI from "../abi/pancakeProfile.json";
import { getPancakeProfileAddress } from "./addressHelpers";
const getContract = (abi: AbiItem, address: string, web3: Web3): Contract => {
return new web3.eth.Contract(abi, address);
};
export const getErc721Contract = (address: string, web3?: Web3): Contract => {
return getContract(erc721Abi as unknown as AbiItem, address, web3);
};
export const getProfileContract = (web3: Web3, chainId: number): Contract => {
return getContract(profileABI as unknown as AbiItem, getPancakeProfileAddress(chainId), web3);
};

View File

@ -0,0 +1,16 @@
import getRpcUrl, { nodes } from "./getRpcUrl";
describe("getRpcUrl", () => {
describe.each`
randomRoll | expectedNode
${0.15} | ${nodes[0]}
${0.35} | ${nodes[1]}
${0.75} | ${nodes[2]}
`("$a + $b", ({ randomRoll, expectedNode }) => {
it("returns random node", () => {
jest.spyOn(global.Math, "random").mockReturnValue(randomRoll);
const nodeUrl = getRpcUrl();
expect(nodeUrl).toEqual(expectedNode);
});
});
});

View File

@ -0,0 +1,19 @@
// Array of available nodes to connect to
export const nodes = [
"https://bsc-dataseed1.ninicoin.io",
"https://bsc-dataseed1.defibit.io",
"https://bsc-dataseed.binance.org",
];
const getRandomIndex = () => {
const lower = 0;
const upper = nodes.length - 1;
return Math.floor(lower + Math.random() * (upper - lower + 1));
};
const getNodeUrl = (): string => {
const randomIndex = getRandomIndex();
return nodes[randomIndex];
};
export default getNodeUrl;

View File

@ -0,0 +1,84 @@
import {
transformProfileResponse,
getAchievementTitle,
getAchievementDescription,
ProfileResponse,
} from "./transformHelpers";
import { Campaign } from "../types";
describe("transformHelpers", () => {
it("transformProfileResponse returns correct profile data", () => {
const rawProfileResponse: ProfileResponse = {
0: "123",
1: "500",
2: "2",
3: "0x12345",
4: "15",
5: true,
};
const profile = transformProfileResponse(rawProfileResponse);
expect(profile).toEqual({
userId: 123,
points: 500,
teamId: 2,
tokenId: 15,
nftAddress: "0x12345",
isActive: true,
});
});
describe("getAchievementTitle", () => {
it("returns correct title for IFO", () => {
const campaign: Campaign = {
id: "55666",
type: "ifo",
title: "Belt",
};
const title = getAchievementTitle(campaign);
expect(title).toEqual({
id: 999,
fallback: "IFO Shopper: Belt",
data: {
name: "Belt",
},
});
});
it("returns default title for other campaign types", () => {
const campaign: Campaign = {
id: "55666",
type: "teambattle",
title: "Easter Gold",
};
const title = getAchievementTitle(campaign);
expect(title).toBe("Easter Gold");
});
});
describe("getAchievementDescription", () => {
it("returns correct description for IFO", () => {
const campaign: Campaign = {
id: "55666",
type: "ifo",
title: "Belt",
};
const description = getAchievementDescription(campaign);
expect(description).toEqual({
id: 999,
fallback: "Committed more than $5 worth of LP in the Belt IFO",
data: {
name: "Belt",
},
});
});
it("returns default description for other campaign types", () => {
const campaign: Campaign = {
id: "55666",
type: "teambattle",
title: "Easter Gold",
description: "Random description",
};
const description = getAchievementDescription(campaign);
expect(description).toBe("Random description");
});
});
});

View File

@ -0,0 +1,53 @@
import { Campaign, TranslatableText, Profile } from "../types";
export type ProfileResponse = {
0: string;
1: string;
2: string;
3: string;
4: string;
5: boolean;
};
export const transformProfileResponse = (profileResponse: ProfileResponse): Partial<Profile> => {
const { 0: userId, 1: numberPoints, 2: teamId, 3: nftAddress, 4: tokenId, 5: isActive } = profileResponse;
return {
userId: Number(userId),
points: Number(numberPoints),
teamId: Number(teamId),
tokenId: Number(tokenId),
nftAddress,
isActive,
};
};
export const getAchievementTitle = (campaign: Campaign): TranslatableText => {
switch (campaign.type) {
case "ifo":
return {
id: 999,
fallback: `IFO Shopper: ${campaign.title}`,
data: {
name: campaign.title as string,
},
};
default:
return campaign.title;
}
};
export const getAchievementDescription = (campaign: Campaign): TranslatableText => {
switch (campaign.type) {
case "ifo":
return {
id: 999,
fallback: `Committed more than $5 worth of LP in the ${campaign.title} IFO`,
data: {
name: campaign.title as string,
},
};
default:
return campaign.description;
}
};

View File

@ -0,0 +1,8 @@
import Web3 from "web3";
import web3NoAccount from "./web3";
describe("web3", () => {
it("returns an instance of Web3", () => {
expect(web3NoAccount).toBeInstanceOf(Web3);
});
});

View File

@ -0,0 +1,10 @@
import Web3 from "web3";
import getRpcUrl from "./getRpcUrl";
const RPC_URL = getRpcUrl();
const httpProvider = new Web3.providers.HttpProvider(RPC_URL, {
timeout: 10000,
});
const web3NoAccount = new Web3(httpProvider);
export default web3NoAccount;

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": ["ES2020", "DOM"],
"target": "ES5",
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.test.ts", "**/__mocks__/*", "src/mocks/**", "jest.setup.js"]
}

View File

@ -0,0 +1,3 @@
{
"plugins": [] // "transform-jsbi-to-bigint"
}

View File

@ -0,0 +1,43 @@
name: CI
env:
CI: true
on:
pull_request:
branches:
- v2
push:
branches:
- v2
jobs:
test:
strategy:
matrix:
node: ['10.x', '12.x']
os: [ubuntu-latest, macOS-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- run: npm install -g yarn
- id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: ${{ matrix.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ matrix.os }}-yarn-
- run: yarn
- run: yarn lint
- run: yarn build
- run: yarn test

2
packages/pancake-swap-sdk/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist
node_modules

View File

@ -0,0 +1 @@
ignore-scripts true

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Noah Zinsmeister
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,53 @@
# Pancakeswap SDK
Forked from the [Uniswap SDK](https://github.com/Uniswap/uniswap-v2-sdk/commit/a88048e9c4198a5bdaea00883ca00c8c8e582605).
You can refer to the Uniswap SDK documentation [uniswap.org](https://uniswap.org/docs/v2/SDK/getting-started/).
## Running tests
To run the tests, follow these steps. You must have at least node v10 and [yarn](https://yarnpkg.com/) installed.
First clone the repository:
```sh
git clone https://github.com/pancakeswap/pancake-swap-sdk.git
```
Move into the pancakeswap-sdk working directory
```sh
cd pancakeswap-sdk/
```
Install dependencies
```sh
yarn install
```
Run tests
```sh
yarn test
```
You should see output like the following:
```sh
yarn run v1.22.4
$ tsdx test
PASS test/constants.test.ts
PASS test/pair.test.ts
PASS test/fraction.test.ts
PASS test/miscellaneous.test.ts
PASS test/entities.test.ts
PASS test/trade.test.ts
Test Suites: 1 skipped, 6 passed, 6 of 7 total
Tests: 3 skipped, 82 passed, 85 total
Snapshots: 0 total
Time: 5.091s
Ran all test suites.
✨ Done in 6.61s.
```

View File

@ -0,0 +1,58 @@
{
"name": "@pancakeswap/sdk",
"license": "MIT",
"version": "2.4.3",
"description": "🛠 An SDK for building applications on top of Pancakeswap.",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"files": [
"dist"
],
"repository": "https://github.com/pancakeswap/pancake-swap-sdk.git",
"keywords": [
"pancakeswap",
"bsc"
],
"module": "dist/sdk.esm.js",
"scripts": {
"lint": "tsdx lint src test",
"build": "tsdx build",
"start": "tsdx watch",
"test": "tsdx test",
"prepublishOnly": "tsdx build"
},
"dependencies": {
"big.js": "^5.2.2",
"decimal.js-light": "^2.5.0",
"jsbi": "^3.1.4",
"tiny-invariant": "^1.1.0",
"tiny-warning": "^1.0.3",
"toformat": "^2.0.0"
},
"peerDependencies": {
"@ethersproject/address": "^5.0.0",
"@ethersproject/contracts": "^5.0.0",
"@ethersproject/networks": "^5.0.0",
"@ethersproject/providers": "^5.0.0",
"@ethersproject/solidity": "^5.0.0"
},
"devDependencies": {
"@ethersproject/address": "^5.0.2",
"@ethersproject/contracts": "^5.0.2",
"@ethersproject/networks": "^5.0.2",
"@ethersproject/providers": "^5.0.5",
"@ethersproject/solidity": "^5.0.2",
"@types/big.js": "^4.0.5",
"@types/jest": "^24.0.25",
"babel-plugin-transform-jsbi-to-bigint": "^1.3.1",
"tsdx": "^0.12.3"
},
"engines": {
"node": ">=10"
},
"prettier": {
"printWidth": 120,
"semi": false,
"singleQuote": true
}
}

View File

@ -0,0 +1,20 @@
[
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{ "name": "", "type": "uint8" }],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [{ "name": "", "type": "address" }],
"name": "balanceOf",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

View File

@ -0,0 +1,707 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Burn",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Mint",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1In",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "Swap",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint112",
"name": "reserve0",
"type": "uint112"
},
{
"indexed": false,
"internalType": "uint112",
"name": "reserve1",
"type": "uint112"
}
],
"name": "Sync",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"constant": true,
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "MINIMUM_LIQUIDITY",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "PERMIT_TYPEHASH",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "burn",
"outputs": [
{
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getReserves",
"outputs": [
{
"internalType": "uint112",
"name": "reserve0",
"type": "uint112"
},
{
"internalType": "uint112",
"name": "reserve1",
"type": "uint112"
},
{
"internalType": "uint32",
"name": "blockTimestampLast",
"type": "uint32"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "kLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "mint",
"outputs": [
{
"internalType": "uint256",
"name": "liquidity",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "nonces",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "permit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "price0CumulativeLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "price1CumulativeLast",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "skim",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "amount0Out",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amount1Out",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "pure",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "sync",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "token0",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "token1",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@ -0,0 +1,57 @@
import JSBI from 'jsbi'
// exports for external consumption
export type BigintIsh = JSBI | number | string
export enum ChainId {
MAINNET = 56,
TESTNET = 97
}
export enum TradeType {
EXACT_INPUT,
EXACT_OUTPUT
}
export enum Rounding {
ROUND_DOWN,
ROUND_HALF_UP,
ROUND_UP
}
export const FACTORY_ADDRESS = '0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73'
export const FACTORY_ADDRESS_MAP = {
[ChainId.MAINNET]: FACTORY_ADDRESS,
[ChainId.TESTNET]: '0x6725f303b657a9451d8ba641348b6761a6cc7a17'
}
export const INIT_CODE_HASH = '0x00fb7f630766e6a796048ea87d01acd3068e8ff67d078148a3fa3f4a84f69bd5'
export const INIT_CODE_HASH_MAP = {
[ChainId.MAINNET]: INIT_CODE_HASH,
[ChainId.TESTNET]: '0xd0d4c4cd0848c93cb4fd1f498d7013ee6bfb25783ea21593d5834f5d250ece66'
}
export const MINIMUM_LIQUIDITY = JSBI.BigInt(1000)
// exports for internal consumption
export const ZERO = JSBI.BigInt(0)
export const ONE = JSBI.BigInt(1)
export const TWO = JSBI.BigInt(2)
export const THREE = JSBI.BigInt(3)
export const FIVE = JSBI.BigInt(5)
export const TEN = JSBI.BigInt(10)
export const _100 = JSBI.BigInt(100)
export const FEES_NUMERATOR = JSBI.BigInt(9975)
export const FEES_DENOMINATOR = JSBI.BigInt(10000)
export enum SolidityType {
uint8 = 'uint8',
uint256 = 'uint256'
}
export const SOLIDITY_TYPE_MAXIMA = {
[SolidityType.uint8]: JSBI.BigInt('0xff'),
[SolidityType.uint256]: JSBI.BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')
}

View File

@ -0,0 +1 @@
declare module 'toformat'

View File

@ -0,0 +1,37 @@
import JSBI from 'jsbi'
import { SolidityType } from '../constants'
import { validateSolidityTypeInstance } from '../utils'
/**
* A currency is any fungible financial instrument on Ethereum, including Ether and all ERC20 tokens.
*
* The only instance of the base class `Currency` is Ether.
*/
export class Currency {
public readonly decimals: number
public readonly symbol?: string
public readonly name?: string
/**
* The only instance of the base class `Currency`.
*/
public static readonly ETHER: Currency = new Currency(18, 'BNB', 'BNB')
/**
* Constructs an instance of the base class `Currency`. The only instance of the base class `Currency` is `Currency.ETHER`.
* @param decimals decimals of the currency
* @param symbol symbol of the currency
* @param name of the currency
*/
protected constructor(decimals: number, symbol?: string, name?: string) {
validateSolidityTypeInstance(JSBI.BigInt(decimals), SolidityType.uint8)
this.decimals = decimals
this.symbol = symbol
this.name = name
}
}
const ETHER = Currency.ETHER
export { ETHER }

View File

@ -0,0 +1,69 @@
import { currencyEquals } from '../token'
import { Currency, ETHER } from '../currency'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import _Big from 'big.js'
import toFormat from 'toformat'
import { BigintIsh, Rounding, TEN, SolidityType } from '../../constants'
import { parseBigintIsh, validateSolidityTypeInstance } from '../../utils'
import { Fraction } from './fraction'
const Big = toFormat(_Big)
export class CurrencyAmount extends Fraction {
public readonly currency: Currency
/**
* Helper that calls the constructor with the ETHER currency
* @param amount ether amount in wei
*/
public static ether(amount: BigintIsh): CurrencyAmount {
return new CurrencyAmount(ETHER, amount)
}
// amount _must_ be raw, i.e. in the native representation
protected constructor(currency: Currency, amount: BigintIsh) {
const parsedAmount = parseBigintIsh(amount)
validateSolidityTypeInstance(parsedAmount, SolidityType.uint256)
super(parsedAmount, JSBI.exponentiate(TEN, JSBI.BigInt(currency.decimals)))
this.currency = currency
}
public get raw(): JSBI {
return this.numerator
}
public add(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.add(this.raw, other.raw))
}
public subtract(other: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(this.currency, other.currency), 'TOKEN')
return new CurrencyAmount(this.currency, JSBI.subtract(this.raw, other.raw))
}
public toSignificant(
significantDigits: number = 6,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
): string {
return super.toSignificant(significantDigits, format, rounding)
}
public toFixed(
decimalPlaces: number = this.currency.decimals,
format?: object,
rounding: Rounding = Rounding.ROUND_DOWN
): string {
invariant(decimalPlaces <= this.currency.decimals, 'DECIMALS')
return super.toFixed(decimalPlaces, format, rounding)
}
public toExact(format: object = { groupSeparator: '' }): string {
Big.DP = this.currency.decimals
return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(format)
}
}

View File

@ -0,0 +1,151 @@
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import _Decimal from 'decimal.js-light'
import _Big, { RoundingMode } from 'big.js'
import toFormat from 'toformat'
import { BigintIsh, Rounding } from '../../constants'
import { ONE } from '../../constants'
import { parseBigintIsh } from '../../utils'
const Decimal = toFormat(_Decimal)
const Big = toFormat(_Big)
const toSignificantRounding = {
[Rounding.ROUND_DOWN]: Decimal.ROUND_DOWN,
[Rounding.ROUND_HALF_UP]: Decimal.ROUND_HALF_UP,
[Rounding.ROUND_UP]: Decimal.ROUND_UP
}
const toFixedRounding = {
[Rounding.ROUND_DOWN]: RoundingMode.RoundDown,
[Rounding.ROUND_HALF_UP]: RoundingMode.RoundHalfUp,
[Rounding.ROUND_UP]: RoundingMode.RoundUp
}
export class Fraction {
public readonly numerator: JSBI
public readonly denominator: JSBI
public constructor(numerator: BigintIsh, denominator: BigintIsh = ONE) {
this.numerator = parseBigintIsh(numerator)
this.denominator = parseBigintIsh(denominator)
}
// performs floor division
public get quotient(): JSBI {
return JSBI.divide(this.numerator, this.denominator)
}
// remainder after floor division
public get remainder(): Fraction {
return new Fraction(JSBI.remainder(this.numerator, this.denominator), this.denominator)
}
public invert(): Fraction {
return new Fraction(this.denominator, this.numerator)
}
public add(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.add(this.numerator, otherParsed.numerator), this.denominator)
}
return new Fraction(
JSBI.add(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
),
JSBI.multiply(this.denominator, otherParsed.denominator)
)
}
public subtract(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
if (JSBI.equal(this.denominator, otherParsed.denominator)) {
return new Fraction(JSBI.subtract(this.numerator, otherParsed.numerator), this.denominator)
}
return new Fraction(
JSBI.subtract(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
),
JSBI.multiply(this.denominator, otherParsed.denominator)
)
}
public lessThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.lessThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
public equalTo(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.equal(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
public greaterThan(other: Fraction | BigintIsh): boolean {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return JSBI.greaterThan(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(otherParsed.numerator, this.denominator)
)
}
public multiply(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.numerator),
JSBI.multiply(this.denominator, otherParsed.denominator)
)
}
public divide(other: Fraction | BigintIsh): Fraction {
const otherParsed = other instanceof Fraction ? other : new Fraction(parseBigintIsh(other))
return new Fraction(
JSBI.multiply(this.numerator, otherParsed.denominator),
JSBI.multiply(this.denominator, otherParsed.numerator)
)
}
public toSignificant(
significantDigits: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
): string {
invariant(Number.isInteger(significantDigits), `${significantDigits} is not an integer.`)
invariant(significantDigits > 0, `${significantDigits} is not positive.`)
Decimal.set({ precision: significantDigits + 1, rounding: toSignificantRounding[rounding] })
const quotient = new Decimal(this.numerator.toString())
.div(this.denominator.toString())
.toSignificantDigits(significantDigits)
return quotient.toFormat(quotient.decimalPlaces(), format)
}
public toFixed(
decimalPlaces: number,
format: object = { groupSeparator: '' },
rounding: Rounding = Rounding.ROUND_HALF_UP
): string {
invariant(Number.isInteger(decimalPlaces), `${decimalPlaces} is not an integer.`)
invariant(decimalPlaces >= 0, `${decimalPlaces} is negative.`)
Big.DP = decimalPlaces
Big.RM = toFixedRounding[rounding]
return new Big(this.numerator.toString()).div(this.denominator.toString()).toFormat(decimalPlaces, format)
}
/**
* Helper method for converting any super class back to a fraction
*/
public get asFraction(): Fraction {
return new Fraction(this.numerator, this.denominator)
}
}

View File

@ -0,0 +1,5 @@
export * from './fraction'
export * from './percent'
export * from './tokenAmount'
export * from './currencyAmount'
export * from './price'

View File

@ -0,0 +1,14 @@
import { Rounding, _100 } from '../../constants'
import { Fraction } from './fraction'
const _100_PERCENT = new Fraction(_100)
export class Percent extends Fraction {
public toSignificant(significantDigits: number = 5, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toSignificant(significantDigits, format, rounding)
}
public toFixed(decimalPlaces: number = 2, format?: object, rounding?: Rounding): string {
return this.multiply(_100_PERCENT).toFixed(decimalPlaces, format, rounding)
}
}

View File

@ -0,0 +1,76 @@
import { Token } from '../token'
import { TokenAmount } from './tokenAmount'
import { currencyEquals } from '../token'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { BigintIsh, Rounding, TEN } from '../../constants'
import { Currency } from '../currency'
import { Route } from '../route'
import { Fraction } from './fraction'
import { CurrencyAmount } from './currencyAmount'
export class Price extends Fraction {
public readonly baseCurrency: Currency // input i.e. denominator
public readonly quoteCurrency: Currency // output i.e. numerator
public readonly scalar: Fraction // used to adjust the raw fraction w/r/t the decimals of the {base,quote}Token
public static fromRoute(route: Route): Price {
const prices: Price[] = []
for (const [i, pair] of route.pairs.entries()) {
prices.push(
route.path[i].equals(pair.token0)
? new Price(pair.reserve0.currency, pair.reserve1.currency, pair.reserve0.raw, pair.reserve1.raw)
: new Price(pair.reserve1.currency, pair.reserve0.currency, pair.reserve1.raw, pair.reserve0.raw)
)
}
return prices.slice(1).reduce((accumulator, currentValue) => accumulator.multiply(currentValue), prices[0])
}
// denominator and numerator _must_ be raw, i.e. in the native representation
public constructor(baseCurrency: Currency, quoteCurrency: Currency, denominator: BigintIsh, numerator: BigintIsh) {
super(numerator, denominator)
this.baseCurrency = baseCurrency
this.quoteCurrency = quoteCurrency
this.scalar = new Fraction(
JSBI.exponentiate(TEN, JSBI.BigInt(baseCurrency.decimals)),
JSBI.exponentiate(TEN, JSBI.BigInt(quoteCurrency.decimals))
)
}
public get raw(): Fraction {
return new Fraction(this.numerator, this.denominator)
}
public get adjusted(): Fraction {
return super.multiply(this.scalar)
}
public invert(): Price {
return new Price(this.quoteCurrency, this.baseCurrency, this.numerator, this.denominator)
}
public multiply(other: Price): Price {
invariant(currencyEquals(this.quoteCurrency, other.baseCurrency), 'TOKEN')
const fraction = super.multiply(other)
return new Price(this.baseCurrency, other.quoteCurrency, fraction.denominator, fraction.numerator)
}
// performs floor division on overflow
public quote(currencyAmount: CurrencyAmount): CurrencyAmount {
invariant(currencyEquals(currencyAmount.currency, this.baseCurrency), 'TOKEN')
if (this.quoteCurrency instanceof Token) {
return new TokenAmount(this.quoteCurrency, super.multiply(currencyAmount.raw).quotient)
}
return CurrencyAmount.ether(super.multiply(currencyAmount.raw).quotient)
}
public toSignificant(significantDigits: number = 6, format?: object, rounding?: Rounding): string {
return this.adjusted.toSignificant(significantDigits, format, rounding)
}
public toFixed(decimalPlaces: number = 4, format?: object, rounding?: Rounding): string {
return this.adjusted.toFixed(decimalPlaces, format, rounding)
}
}

View File

@ -0,0 +1,26 @@
import { CurrencyAmount } from './currencyAmount'
import { Token } from '../token'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { BigintIsh } from '../../constants'
export class TokenAmount extends CurrencyAmount {
public readonly token: Token
// amount _must_ be raw, i.e. in the native representation
public constructor(token: Token, amount: BigintIsh) {
super(token, amount)
this.token = token
}
public add(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.add(this.raw, other.raw))
}
public subtract(other: TokenAmount): TokenAmount {
invariant(this.token.equals(other.token), 'TOKEN')
return new TokenAmount(this.token, JSBI.subtract(this.raw, other.raw))
}
}

View File

@ -0,0 +1,7 @@
export * from './token'
export * from './pair'
export * from './route'
export * from './trade'
export * from './currency'
export * from './fractions'

View File

@ -0,0 +1,229 @@
import { Price } from './fractions/price'
import { TokenAmount } from './fractions/tokenAmount'
import invariant from 'tiny-invariant'
import JSBI from 'jsbi'
import { pack, keccak256 } from '@ethersproject/solidity'
import { getCreate2Address } from '@ethersproject/address'
import {
BigintIsh,
FACTORY_ADDRESS_MAP,
INIT_CODE_HASH_MAP,
MINIMUM_LIQUIDITY,
ZERO,
ONE,
FIVE,
FEES_NUMERATOR,
FEES_DENOMINATOR,
ChainId
} from '../constants'
import { sqrt, parseBigintIsh } from '../utils'
import { InsufficientReservesError, InsufficientInputAmountError } from '../errors'
import { Token } from './token'
let PAIR_ADDRESS_CACHE: { [key: string]: string } = {}
const composeKey = (token0: Token, token1: Token) => `${token0.chainId}-${token0.address}-${token1.address}`
export class Pair {
public readonly liquidityToken: Token
private readonly tokenAmounts: [TokenAmount, TokenAmount]
public static getAddress(tokenA: Token, tokenB: Token): string {
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA] // does safety checks
const key = composeKey(token0, token1)
if (PAIR_ADDRESS_CACHE?.[key] === undefined) {
PAIR_ADDRESS_CACHE = {
...PAIR_ADDRESS_CACHE,
[key]: getCreate2Address(
FACTORY_ADDRESS_MAP[token0.chainId],
keccak256(['bytes'], [pack(['address', 'address'], [token0.address, token1.address])]),
INIT_CODE_HASH_MAP[token0.chainId]
)
}
}
return PAIR_ADDRESS_CACHE[key]
}
public constructor(tokenAmountA: TokenAmount, tokenAmountB: TokenAmount) {
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
this.liquidityToken = new Token(
tokenAmounts[0].token.chainId,
Pair.getAddress(tokenAmounts[0].token, tokenAmounts[1].token),
18,
'Cake-LP',
'Pancake LPs'
)
this.tokenAmounts = tokenAmounts as [TokenAmount, TokenAmount]
}
/**
* Returns true if the token is either token0 or token1
* @param token to check
*/
public involvesToken(token: Token): boolean {
return token.equals(this.token0) || token.equals(this.token1)
}
/**
* Returns the current mid price of the pair in terms of token0, i.e. the ratio of reserve1 to reserve0
*/
public get token0Price(): Price {
return new Price(this.token0, this.token1, this.tokenAmounts[0].raw, this.tokenAmounts[1].raw)
}
/**
* Returns the current mid price of the pair in terms of token1, i.e. the ratio of reserve0 to reserve1
*/
public get token1Price(): Price {
return new Price(this.token1, this.token0, this.tokenAmounts[1].raw, this.tokenAmounts[0].raw)
}
/**
* Return the price of the given token in terms of the other token in the pair.
* @param token token to return price of
*/
public priceOf(token: Token): Price {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.token0Price : this.token1Price
}
/**
* Returns the chain ID of the tokens in the pair.
*/
public get chainId(): ChainId {
return this.token0.chainId
}
public get token0(): Token {
return this.tokenAmounts[0].token
}
public get token1(): Token {
return this.tokenAmounts[1].token
}
public get reserve0(): TokenAmount {
return this.tokenAmounts[0]
}
public get reserve1(): TokenAmount {
return this.tokenAmounts[1]
}
public reserveOf(token: Token): TokenAmount {
invariant(this.involvesToken(token), 'TOKEN')
return token.equals(this.token0) ? this.reserve0 : this.reserve1
}
public getOutputAmount(inputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(inputAmount.token), 'TOKEN')
if (JSBI.equal(this.reserve0.raw, ZERO) || JSBI.equal(this.reserve1.raw, ZERO)) {
throw new InsufficientReservesError()
}
const inputReserve = this.reserveOf(inputAmount.token)
const outputReserve = this.reserveOf(inputAmount.token.equals(this.token0) ? this.token1 : this.token0)
const inputAmountWithFee = JSBI.multiply(inputAmount.raw, FEES_NUMERATOR)
const numerator = JSBI.multiply(inputAmountWithFee, outputReserve.raw)
const denominator = JSBI.add(JSBI.multiply(inputReserve.raw, FEES_DENOMINATOR), inputAmountWithFee)
const outputAmount = new TokenAmount(
inputAmount.token.equals(this.token0) ? this.token1 : this.token0,
JSBI.divide(numerator, denominator)
)
if (JSBI.equal(outputAmount.raw, ZERO)) {
throw new InsufficientInputAmountError()
}
return [outputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
public getInputAmount(outputAmount: TokenAmount): [TokenAmount, Pair] {
invariant(this.involvesToken(outputAmount.token), 'TOKEN')
if (
JSBI.equal(this.reserve0.raw, ZERO) ||
JSBI.equal(this.reserve1.raw, ZERO) ||
JSBI.greaterThanOrEqual(outputAmount.raw, this.reserveOf(outputAmount.token).raw)
) {
throw new InsufficientReservesError()
}
const outputReserve = this.reserveOf(outputAmount.token)
const inputReserve = this.reserveOf(outputAmount.token.equals(this.token0) ? this.token1 : this.token0)
const numerator = JSBI.multiply(JSBI.multiply(inputReserve.raw, outputAmount.raw), FEES_DENOMINATOR)
const denominator = JSBI.multiply(JSBI.subtract(outputReserve.raw, outputAmount.raw), FEES_NUMERATOR)
const inputAmount = new TokenAmount(
outputAmount.token.equals(this.token0) ? this.token1 : this.token0,
JSBI.add(JSBI.divide(numerator, denominator), ONE)
)
return [inputAmount, new Pair(inputReserve.add(inputAmount), outputReserve.subtract(outputAmount))]
}
public getLiquidityMinted(
totalSupply: TokenAmount,
tokenAmountA: TokenAmount,
tokenAmountB: TokenAmount
): TokenAmount {
invariant(totalSupply.token.equals(this.liquidityToken), 'LIQUIDITY')
const tokenAmounts = tokenAmountA.token.sortsBefore(tokenAmountB.token) // does safety checks
? [tokenAmountA, tokenAmountB]
: [tokenAmountB, tokenAmountA]
invariant(tokenAmounts[0].token.equals(this.token0) && tokenAmounts[1].token.equals(this.token1), 'TOKEN')
let liquidity: JSBI
if (JSBI.equal(totalSupply.raw, ZERO)) {
liquidity = JSBI.subtract(sqrt(JSBI.multiply(tokenAmounts[0].raw, tokenAmounts[1].raw)), MINIMUM_LIQUIDITY)
} else {
const amount0 = JSBI.divide(JSBI.multiply(tokenAmounts[0].raw, totalSupply.raw), this.reserve0.raw)
const amount1 = JSBI.divide(JSBI.multiply(tokenAmounts[1].raw, totalSupply.raw), this.reserve1.raw)
liquidity = JSBI.lessThanOrEqual(amount0, amount1) ? amount0 : amount1
}
if (!JSBI.greaterThan(liquidity, ZERO)) {
throw new InsufficientInputAmountError()
}
return new TokenAmount(this.liquidityToken, liquidity)
}
public getLiquidityValue(
token: Token,
totalSupply: TokenAmount,
liquidity: TokenAmount,
feeOn: boolean = false,
kLast?: BigintIsh
): TokenAmount {
invariant(this.involvesToken(token), 'TOKEN')
invariant(totalSupply.token.equals(this.liquidityToken), 'TOTAL_SUPPLY')
invariant(liquidity.token.equals(this.liquidityToken), 'LIQUIDITY')
invariant(JSBI.lessThanOrEqual(liquidity.raw, totalSupply.raw), 'LIQUIDITY')
let totalSupplyAdjusted: TokenAmount
if (!feeOn) {
totalSupplyAdjusted = totalSupply
} else {
invariant(!!kLast, 'K_LAST')
const kLastParsed = parseBigintIsh(kLast)
if (!JSBI.equal(kLastParsed, ZERO)) {
const rootK = sqrt(JSBI.multiply(this.reserve0.raw, this.reserve1.raw))
const rootKLast = sqrt(kLastParsed)
if (JSBI.greaterThan(rootK, rootKLast)) {
const numerator = JSBI.multiply(totalSupply.raw, JSBI.subtract(rootK, rootKLast))
const denominator = JSBI.add(JSBI.multiply(rootK, FIVE), rootKLast)
const feeLiquidity = JSBI.divide(numerator, denominator)
totalSupplyAdjusted = totalSupply.add(new TokenAmount(this.liquidityToken, feeLiquidity))
} else {
totalSupplyAdjusted = totalSupply
}
} else {
totalSupplyAdjusted = totalSupply
}
}
return new TokenAmount(
token,
JSBI.divide(JSBI.multiply(liquidity.raw, this.reserveOf(token).raw), totalSupplyAdjusted.raw)
)
}
}

View File

@ -0,0 +1,52 @@
import { ChainId } from '../constants'
import invariant from 'tiny-invariant'
import { Currency, ETHER } from './currency'
import { Token, WETH } from './token'
import { Pair } from './pair'
import { Price } from './fractions/price'
export class Route {
public readonly pairs: Pair[]
public readonly path: Token[]
public readonly input: Currency
public readonly output: Currency
public readonly midPrice: Price
public constructor(pairs: Pair[], input: Currency, output?: Currency) {
invariant(pairs.length > 0, 'PAIRS')
invariant(
pairs.every(pair => pair.chainId === pairs[0].chainId),
'CHAIN_IDS'
)
invariant(
(input instanceof Token && pairs[0].involvesToken(input)) ||
(input === ETHER && pairs[0].involvesToken(WETH[pairs[0].chainId])),
'INPUT'
)
invariant(
typeof output === 'undefined' ||
(output instanceof Token && pairs[pairs.length - 1].involvesToken(output)) ||
(output === ETHER && pairs[pairs.length - 1].involvesToken(WETH[pairs[0].chainId])),
'OUTPUT'
)
const path: Token[] = [input instanceof Token ? input : WETH[pairs[0].chainId]]
for (const [i, pair] of pairs.entries()) {
const currentInput = path[i]
invariant(currentInput.equals(pair.token0) || currentInput.equals(pair.token1), 'PATH')
const output = currentInput.equals(pair.token0) ? pair.token1 : pair.token0
path.push(output)
}
this.pairs = pairs
this.path = path
this.midPrice = Price.fromRoute(this)
this.input = input
this.output = output ?? path[path.length - 1]
}
public get chainId(): ChainId {
return this.pairs[0].chainId
}
}

View File

@ -0,0 +1,85 @@
import invariant from 'tiny-invariant'
import { ChainId } from '../constants'
import { validateAndParseAddress } from '../utils'
import { Currency } from './currency'
/**
* Represents an ERC20 token with a unique address and some metadata.
*/
export class Token extends Currency {
public readonly chainId: ChainId
public readonly address: string
public readonly projectLink?: string
public constructor(
chainId: ChainId,
address: string,
decimals: number,
symbol?: string,
name?: string,
projectLink?: string
) {
super(decimals, symbol, name)
this.chainId = chainId
this.address = validateAndParseAddress(address)
this.projectLink = projectLink
}
/**
* Returns true if the two tokens are equivalent, i.e. have the same chainId and address.
* @param other other token to compare
*/
public equals(other: Token): boolean {
// short circuit on reference equality
if (this === other) {
return true
}
return this.chainId === other.chainId && this.address === other.address
}
/**
* Returns true if the address of this token sorts before the address of the other token
* @param other other token to compare
* @throws if the tokens have the same address
* @throws if the tokens are on different chains
*/
public sortsBefore(other: Token): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
invariant(this.address !== other.address, 'ADDRESSES')
return this.address.toLowerCase() < other.address.toLowerCase()
}
}
/**
* Compares two currencies for equality
*/
export function currencyEquals(currencyA: Currency, currencyB: Currency): boolean {
if (currencyA instanceof Token && currencyB instanceof Token) {
return currencyA.equals(currencyB)
} else if (currencyA instanceof Token) {
return false
} else if (currencyB instanceof Token) {
return false
} else {
return currencyA === currencyB
}
}
export const WETH = {
[ChainId.MAINNET]: new Token(
ChainId.MAINNET,
'0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
18,
'WBNB',
'Wrapped BNB',
'https://www.binance.org'
),
[ChainId.TESTNET]: new Token(
ChainId.TESTNET,
'0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd',
18,
'WBNB',
'Wrapped BNB',
'https://www.binance.org'
)
}

View File

@ -0,0 +1,411 @@
import invariant from 'tiny-invariant'
import { InsufficientInputAmountError, InsufficientReservesError } from '..'
import { ChainId, ONE, TradeType, ZERO } from '../constants'
import { sortedInsert } from '../utils'
import { Currency, ETHER } from './currency'
import { CurrencyAmount } from './fractions/currencyAmount'
import { Fraction } from './fractions/fraction'
import { Percent } from './fractions/percent'
import { Price } from './fractions/price'
import { TokenAmount } from './fractions/tokenAmount'
import { Pair } from './pair'
import { Route } from './route'
import { currencyEquals, Token, WETH } from './token'
/**
* Returns the percent difference between the mid price and the execution price, i.e. price impact.
* @param midPrice mid price before the trade
* @param inputAmount the input amount of the trade
* @param outputAmount the output amount of the trade
*/
function computePriceImpact(midPrice: Price, inputAmount: CurrencyAmount, outputAmount: CurrencyAmount): Percent {
const exactQuote = midPrice.raw.multiply(inputAmount.raw)
// calculate slippage := (exactQuote - outputAmount) / exactQuote
const slippage = exactQuote.subtract(outputAmount.raw).divide(exactQuote)
return new Percent(slippage.numerator, slippage.denominator)
}
// minimal interface so the input output comparator may be shared across types
interface InputOutput {
readonly inputAmount: CurrencyAmount
readonly outputAmount: CurrencyAmount
}
// comparator function that allows sorting trades by their output amounts, in decreasing order, and then input amounts
// in increasing order. i.e. the best trades have the most outputs for the least inputs and are sorted first
export function inputOutputComparator(a: InputOutput, b: InputOutput): number {
// must have same input and output token for comparison
invariant(currencyEquals(a.inputAmount.currency, b.inputAmount.currency), 'INPUT_CURRENCY')
invariant(currencyEquals(a.outputAmount.currency, b.outputAmount.currency), 'OUTPUT_CURRENCY')
if (a.outputAmount.equalTo(b.outputAmount)) {
if (a.inputAmount.equalTo(b.inputAmount)) {
return 0
}
// trade A requires less input than trade B, so A should come first
if (a.inputAmount.lessThan(b.inputAmount)) {
return -1
} else {
return 1
}
} else {
// tradeA has less output than trade B, so should come second
if (a.outputAmount.lessThan(b.outputAmount)) {
return 1
} else {
return -1
}
}
}
// extension of the input output comparator that also considers other dimensions of the trade in ranking them
export function tradeComparator(a: Trade, b: Trade) {
const ioComp = inputOutputComparator(a, b)
if (ioComp !== 0) {
return ioComp
}
// consider lowest slippage next, since these are less likely to fail
if (a.priceImpact.lessThan(b.priceImpact)) {
return -1
} else if (a.priceImpact.greaterThan(b.priceImpact)) {
return 1
}
// finally consider the number of hops since each hop costs gas
return a.route.path.length - b.route.path.length
}
export interface BestTradeOptions {
// how many results to return
maxNumResults?: number
// the maximum number of hops a trade should contain
maxHops?: number
}
/**
* Given a currency amount and a chain ID, returns the equivalent representation as the token amount.
* In other words, if the currency is ETHER, returns the WETH token amount for the given chain. Otherwise, returns
* the input currency amount.
*/
function wrappedAmount(currencyAmount: CurrencyAmount, chainId: ChainId): TokenAmount {
if (currencyAmount instanceof TokenAmount) return currencyAmount
if (currencyAmount.currency === ETHER) return new TokenAmount(WETH[chainId], currencyAmount.raw)
invariant(false, 'CURRENCY')
}
function wrappedCurrency(currency: Currency, chainId: ChainId): Token {
if (currency instanceof Token) return currency
if (currency === ETHER) return WETH[chainId]
invariant(false, 'CURRENCY')
}
/**
* Represents a trade executed against a list of pairs.
* Does not account for slippage, i.e. trades that front run this trade and move the price.
*/
export class Trade {
/**
* The route of the trade, i.e. which pairs the trade goes through.
*/
public readonly route: Route
/**
* The type of the trade, either exact in or exact out.
*/
public readonly tradeType: TradeType
/**
* The input amount for the trade assuming no slippage.
*/
public readonly inputAmount: CurrencyAmount
/**
* The output amount for the trade assuming no slippage.
*/
public readonly outputAmount: CurrencyAmount
/**
* The price expressed in terms of output amount/input amount.
*/
public readonly executionPrice: Price
/**
* The mid price after the trade executes assuming no slippage.
*/
public readonly nextMidPrice: Price
/**
* The percent difference between the mid price before the trade and the trade execution price.
*/
public readonly priceImpact: Percent
/**
* Constructs an exact in trade with the given amount in and route
* @param route route of the exact in trade
* @param amountIn the amount being passed in
*/
public static exactIn(route: Route, amountIn: CurrencyAmount): Trade {
return new Trade(route, amountIn, TradeType.EXACT_INPUT)
}
/**
* Constructs an exact out trade with the given amount out and route
* @param route route of the exact out trade
* @param amountOut the amount returned by the trade
*/
public static exactOut(route: Route, amountOut: CurrencyAmount): Trade {
return new Trade(route, amountOut, TradeType.EXACT_OUTPUT)
}
public constructor(route: Route, amount: CurrencyAmount, tradeType: TradeType) {
const amounts: TokenAmount[] = new Array(route.path.length)
const nextPairs: Pair[] = new Array(route.pairs.length)
if (tradeType === TradeType.EXACT_INPUT) {
invariant(currencyEquals(amount.currency, route.input), 'INPUT')
amounts[0] = wrappedAmount(amount, route.chainId)
for (let i = 0; i < route.path.length - 1; i++) {
const pair = route.pairs[i]
const [outputAmount, nextPair] = pair.getOutputAmount(amounts[i])
amounts[i + 1] = outputAmount
nextPairs[i] = nextPair
}
} else {
invariant(currencyEquals(amount.currency, route.output), 'OUTPUT')
amounts[amounts.length - 1] = wrappedAmount(amount, route.chainId)
for (let i = route.path.length - 1; i > 0; i--) {
const pair = route.pairs[i - 1]
const [inputAmount, nextPair] = pair.getInputAmount(amounts[i])
amounts[i - 1] = inputAmount
nextPairs[i - 1] = nextPair
}
}
this.route = route
this.tradeType = tradeType
this.inputAmount =
tradeType === TradeType.EXACT_INPUT
? amount
: route.input === ETHER
? CurrencyAmount.ether(amounts[0].raw)
: amounts[0]
this.outputAmount =
tradeType === TradeType.EXACT_OUTPUT
? amount
: route.output === ETHER
? CurrencyAmount.ether(amounts[amounts.length - 1].raw)
: amounts[amounts.length - 1]
this.executionPrice = new Price(
this.inputAmount.currency,
this.outputAmount.currency,
this.inputAmount.raw,
this.outputAmount.raw
)
this.nextMidPrice = Price.fromRoute(new Route(nextPairs, route.input))
this.priceImpact = computePriceImpact(route.midPrice, this.inputAmount, this.outputAmount)
}
/**
* Get the minimum amount that must be received from this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public minimumAmountOut(slippageTolerance: Percent): CurrencyAmount {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_OUTPUT) {
return this.outputAmount
} else {
const slippageAdjustedAmountOut = new Fraction(ONE)
.add(slippageTolerance)
.invert()
.multiply(this.outputAmount.raw).quotient
return this.outputAmount instanceof TokenAmount
? new TokenAmount(this.outputAmount.token, slippageAdjustedAmountOut)
: CurrencyAmount.ether(slippageAdjustedAmountOut)
}
}
/**
* Get the maximum amount in that can be spent via this trade for the given slippage tolerance
* @param slippageTolerance tolerance of unfavorable slippage from the execution price of this trade
*/
public maximumAmountIn(slippageTolerance: Percent): CurrencyAmount {
invariant(!slippageTolerance.lessThan(ZERO), 'SLIPPAGE_TOLERANCE')
if (this.tradeType === TradeType.EXACT_INPUT) {
return this.inputAmount
} else {
const slippageAdjustedAmountIn = new Fraction(ONE).add(slippageTolerance).multiply(this.inputAmount.raw).quotient
return this.inputAmount instanceof TokenAmount
? new TokenAmount(this.inputAmount.token, slippageAdjustedAmountIn)
: CurrencyAmount.ether(slippageAdjustedAmountIn)
}
}
/**
* Given a list of pairs, and a fixed amount in, returns the top `maxNumResults` trades that go from an input token
* amount to an output token, making at most `maxHops` hops.
* Note this does not consider aggregation, as routes are linear. It's possible a better route exists by splitting
* the amount in among multiple routes.
* @param pairs the pairs to consider in finding the best trade
* @param currencyAmountIn exact amount of input currency to spend
* @param currencyOut the desired currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountIn used in recursion; the original value of the currencyAmountIn parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactIn(
pairs: Pair[],
currencyAmountIn: CurrencyAmount,
currencyOut: Currency,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountIn: CurrencyAmount = currencyAmountIn,
bestTrades: Trade[] = []
): Trade[] {
invariant(pairs.length > 0, 'PAIRS')
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountIn === currencyAmountIn || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined =
currencyAmountIn instanceof TokenAmount
? currencyAmountIn.token.chainId
: currencyOut instanceof Token
? currencyOut.chainId
: undefined
invariant(chainId !== undefined, 'CHAIN_ID')
const amountIn = wrappedAmount(currencyAmountIn, chainId)
const tokenOut = wrappedCurrency(currencyOut, chainId)
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountIn.token) && !pair.token1.equals(amountIn.token)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
let amountOut: TokenAmount
try {
;[amountOut] = pair.getOutputAmount(amountIn)
} catch (error) {
// input too low
if ((error as InsufficientInputAmountError).isInsufficientInputAmountError) {
continue
}
throw error
}
// we have arrived at the output token, so this is the final trade of one of the paths
if (amountOut.token.equals(tokenOut)) {
sortedInsert(
bestTrades,
new Trade(
new Route([...currentPairs, pair], originalAmountIn.currency, currencyOut),
originalAmountIn,
TradeType.EXACT_INPUT
),
maxNumResults,
tradeComparator
)
} else if (maxHops > 1 && pairs.length > 1) {
const pairsExcludingThisPair = pairs.slice(0, i).concat(pairs.slice(i + 1, pairs.length))
// otherwise, consider all the other paths that lead from this token as long as we have not exceeded maxHops
Trade.bestTradeExactIn(
pairsExcludingThisPair,
amountOut,
currencyOut,
{
maxNumResults,
maxHops: maxHops - 1
},
[...currentPairs, pair],
originalAmountIn,
bestTrades
)
}
}
return bestTrades
}
/**
* similar to the above method but instead targets a fixed output amount
* given a list of pairs, and a fixed amount out, returns the top `maxNumResults` trades that go from an input token
* to an output token amount, making at most `maxHops` hops
* note this does not consider aggregation, as routes are linear. it's possible a better route exists by splitting
* the amount in among multiple routes.
* @param pairs the pairs to consider in finding the best trade
* @param currencyIn the currency to spend
* @param currencyAmountOut the exact amount of currency out
* @param maxNumResults maximum number of results to return
* @param maxHops maximum number of hops a returned trade can make, e.g. 1 hop goes through a single pair
* @param currentPairs used in recursion; the current list of pairs
* @param originalAmountOut used in recursion; the original value of the currencyAmountOut parameter
* @param bestTrades used in recursion; the current list of best trades
*/
public static bestTradeExactOut(
pairs: Pair[],
currencyIn: Currency,
currencyAmountOut: CurrencyAmount,
{ maxNumResults = 3, maxHops = 3 }: BestTradeOptions = {},
// used in recursion.
currentPairs: Pair[] = [],
originalAmountOut: CurrencyAmount = currencyAmountOut,
bestTrades: Trade[] = []
): Trade[] {
invariant(pairs.length > 0, 'PAIRS')
invariant(maxHops > 0, 'MAX_HOPS')
invariant(originalAmountOut === currencyAmountOut || currentPairs.length > 0, 'INVALID_RECURSION')
const chainId: ChainId | undefined =
currencyAmountOut instanceof TokenAmount
? currencyAmountOut.token.chainId
: currencyIn instanceof Token
? currencyIn.chainId
: undefined
invariant(chainId !== undefined, 'CHAIN_ID')
const amountOut = wrappedAmount(currencyAmountOut, chainId)
const tokenIn = wrappedCurrency(currencyIn, chainId)
for (let i = 0; i < pairs.length; i++) {
const pair = pairs[i]
// pair irrelevant
if (!pair.token0.equals(amountOut.token) && !pair.token1.equals(amountOut.token)) continue
if (pair.reserve0.equalTo(ZERO) || pair.reserve1.equalTo(ZERO)) continue
let amountIn: TokenAmount
try {
;[amountIn] = pair.getInputAmount(amountOut)
} catch (error) {
// not enough liquidity in this pair
if ((error as InsufficientReservesError).isInsufficientReservesError) {
continue
}
throw error
}
// we have arrived at the input token, so this is the first trade of one of the paths
if (amountIn.token.equals(tokenIn)) {
sortedInsert(
bestTrades,
new Trade(
new Route([pair, ...currentPairs], currencyIn, originalAmountOut.currency),
originalAmountOut,
TradeType.EXACT_OUTPUT
),
maxNumResults,
tradeComparator
)
} else if (maxHops > 1 && pairs.length > 1) {
const pairsExcludingThisPair = pairs.slice(0, i).concat(pairs.slice(i + 1, pairs.length))
// otherwise, consider all the other paths that arrive at this token as long as we have not exceeded maxHops
Trade.bestTradeExactOut(
pairsExcludingThisPair,
currencyIn,
amountIn,
{
maxNumResults,
maxHops: maxHops - 1
},
[pair, ...currentPairs],
originalAmountOut,
bestTrades
)
}
}
return bestTrades
}
}

View File

@ -0,0 +1,30 @@
// see https://stackoverflow.com/a/41102306
const CAN_SET_PROTOTYPE = 'setPrototypeOf' in Object
/**
* Indicates that the pair has insufficient reserves for a desired output amount. I.e. the amount of output cannot be
* obtained by sending any amount of input.
*/
export class InsufficientReservesError extends Error {
public readonly isInsufficientReservesError: true = true
public constructor() {
super()
this.name = this.constructor.name
if (CAN_SET_PROTOTYPE) Object.setPrototypeOf(this, new.target.prototype)
}
}
/**
* Indicates that the input amount is too small to produce any amount of output. I.e. the amount of input sent is less
* than the price of a single unit of output after fees.
*/
export class InsufficientInputAmountError extends Error {
public readonly isInsufficientInputAmountError: true = true
public constructor() {
super()
this.name = this.constructor.name
if (CAN_SET_PROTOTYPE) Object.setPrototypeOf(this, new.target.prototype)
}
}

View File

@ -0,0 +1,75 @@
import { Contract } from '@ethersproject/contracts'
import { getNetwork } from '@ethersproject/networks'
import { getDefaultProvider } from '@ethersproject/providers'
import { TokenAmount } from './entities/fractions/tokenAmount'
import { Pair } from './entities/pair'
import IPancakePair from './abis/IPancakePair.json'
import invariant from 'tiny-invariant'
import ERC20 from './abis/ERC20.json'
import { ChainId } from './constants'
import { Token } from './entities/token'
let TOKEN_DECIMALS_CACHE: { [chainId: number]: { [address: string]: number } } = {
[ChainId.MAINNET]: {
'0xE0B7927c4aF23765Cb51314A0E0521A9645F0E2A': 9 // DGD
}
}
/**
* Contains methods for constructing instances of pairs and tokens from on-chain data.
*/
export abstract class Fetcher {
/**
* Cannot be constructed.
*/
private constructor() {}
/**
* Fetch information for a given token on the given chain, using the given ethers provider.
* @param chainId chain of the token
* @param address address of the token on the chain
* @param provider provider used to fetch the token
* @param symbol optional symbol of the token
* @param name optional name of the token
*/
public static async fetchTokenData(
chainId: ChainId,
address: string,
provider = getDefaultProvider(getNetwork(chainId)),
symbol?: string,
name?: string
): Promise<Token> {
const parsedDecimals =
typeof TOKEN_DECIMALS_CACHE?.[chainId]?.[address] === 'number'
? TOKEN_DECIMALS_CACHE[chainId][address]
: await new Contract(address, ERC20, provider).decimals().then((decimals: number): number => {
TOKEN_DECIMALS_CACHE = {
...TOKEN_DECIMALS_CACHE,
[chainId]: {
...TOKEN_DECIMALS_CACHE?.[chainId],
[address]: decimals
}
}
return decimals
})
return new Token(chainId, address, parsedDecimals, symbol, name)
}
/**
* Fetches information about a pair and constructs a pair from the given two tokens.
* @param tokenA first token
* @param tokenB second token
* @param provider the provider to use to fetch the data
*/
public static async fetchPairData(
tokenA: Token,
tokenB: Token,
provider = getDefaultProvider(getNetwork(tokenA.chainId))
): Promise<Pair> {
invariant(tokenA.chainId === tokenB.chainId, 'CHAIN_ID')
const address = Pair.getAddress(tokenA, tokenB)
const [reserves0, reserves1] = await new Contract(address, IPancakePair, provider).getReserves()
const balances = tokenA.sortsBefore(tokenB) ? [reserves0, reserves1] : [reserves1, reserves0]
return new Pair(new TokenAmount(tokenA, balances[0]), new TokenAmount(tokenB, balances[1]))
}
}

View File

@ -0,0 +1,19 @@
import JSBI from 'jsbi'
export { JSBI }
export {
BigintIsh,
ChainId,
TradeType,
Rounding,
FACTORY_ADDRESS,
FACTORY_ADDRESS_MAP,
INIT_CODE_HASH,
INIT_CODE_HASH_MAP,
MINIMUM_LIQUIDITY
} from './constants'
export * from './errors'
export * from './entities'
export * from './router'
export * from './fetcher'

View File

@ -0,0 +1,144 @@
import { TradeType } from './constants'
import invariant from 'tiny-invariant'
import { validateAndParseAddress } from './utils'
import { CurrencyAmount, ETHER, Percent, Trade } from './entities'
/**
* Options for producing the arguments to send call to the router.
*/
export interface TradeOptions {
/**
* How much the execution price is allowed to move unfavorably from the trade execution price.
*/
allowedSlippage: Percent
/**
* How long the swap is valid until it expires, in seconds.
* This will be used to produce a `deadline` parameter which is computed from when the swap call parameters
* are generated.
*/
ttl: number
/**
* The account that should receive the output of the swap.
*/
recipient: string
/**
* Whether any of the tokens in the path are fee on transfer tokens, which should be handled with special methods
*/
feeOnTransfer?: boolean
}
export interface TradeOptionsDeadline extends Omit<TradeOptions, 'ttl'> {
/**
* When the transaction expires.
* This is an atlernate to specifying the ttl, for when you do not want to use local time.
*/
deadline: number
}
/**
* The parameters to use in the call to the Pancake Router to execute a trade.
*/
export interface SwapParameters {
/**
* The method to call on the Pancake Router.
*/
methodName: string
/**
* The arguments to pass to the method, all hex encoded.
*/
args: (string | string[])[]
/**
* The amount of wei to send in hex.
*/
value: string
}
function toHex(currencyAmount: CurrencyAmount) {
return `0x${currencyAmount.raw.toString(16)}`
}
const ZERO_HEX = '0x0'
/**
* Represents the Pancake Router, and has static methods for helping execute trades.
*/
export abstract class Router {
/**
* Cannot be constructed.
*/
private constructor() {}
/**
* Produces the on-chain method name to call and the hex encoded parameters to pass as arguments for a given trade.
* @param trade to produce call parameters for
* @param options options for the call parameters
*/
public static swapCallParameters(trade: Trade, options: TradeOptions | TradeOptionsDeadline): SwapParameters {
const etherIn = trade.inputAmount.currency === ETHER
const etherOut = trade.outputAmount.currency === ETHER
// the router does not support both ether in and out
invariant(!(etherIn && etherOut), 'ETHER_IN_OUT')
invariant(!('ttl' in options) || options.ttl > 0, 'TTL')
const to: string = validateAndParseAddress(options.recipient)
const amountIn: string = toHex(trade.maximumAmountIn(options.allowedSlippage))
const amountOut: string = toHex(trade.minimumAmountOut(options.allowedSlippage))
const path: string[] = trade.route.path.map((token) => token.address)
const deadline =
'ttl' in options
? `0x${(Math.floor(new Date().getTime() / 1000) + options.ttl).toString(16)}`
: `0x${options.deadline.toString(16)}`
const useFeeOnTransfer = Boolean(options.feeOnTransfer)
let methodName: string
let args: (string | string[])[]
let value: string
switch (trade.tradeType) {
case TradeType.EXACT_INPUT:
if (etherIn) {
methodName = useFeeOnTransfer ? 'swapExactETHForTokensSupportingFeeOnTransferTokens' : 'swapExactETHForTokens'
// (uint amountOutMin, address[] calldata path, address to, uint deadline)
args = [amountOut, path, to, deadline]
value = amountIn
} else if (etherOut) {
methodName = useFeeOnTransfer ? 'swapExactTokensForETHSupportingFeeOnTransferTokens' : 'swapExactTokensForETH'
// (uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
args = [amountIn, amountOut, path, to, deadline]
value = ZERO_HEX
} else {
methodName = useFeeOnTransfer
? 'swapExactTokensForTokensSupportingFeeOnTransferTokens'
: 'swapExactTokensForTokens'
// (uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
args = [amountIn, amountOut, path, to, deadline]
value = ZERO_HEX
}
break
case TradeType.EXACT_OUTPUT:
invariant(!useFeeOnTransfer, 'EXACT_OUT_FOT')
if (etherIn) {
methodName = 'swapETHForExactTokens'
// (uint amountOut, address[] calldata path, address to, uint deadline)
args = [amountOut, path, to, deadline]
value = amountIn
} else if (etherOut) {
methodName = 'swapTokensForExactETH'
// (uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
args = [amountOut, amountIn, path, to, deadline]
value = ZERO_HEX
} else {
methodName = 'swapTokensForExactTokens'
// (uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
args = [amountOut, amountIn, path, to, deadline]
value = ZERO_HEX
}
break
}
return {
methodName,
args,
value,
}
}
}

Some files were not shown because too many files have changed in this diff Show More