├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── biome.json
├── examples
└── next-js-app
│ ├── .dockerignore
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── Dockerfile
│ ├── README.md
│ ├── app
│ ├── globals.css
│ ├── layout.tsx
│ ├── page.tsx
│ ├── providers.tsx
│ ├── swap
│ │ ├── actions
│ │ │ └── build-swap-transaction.ts
│ │ ├── components
│ │ │ ├── referral-form.tsx
│ │ │ ├── swap-button.tsx
│ │ │ ├── swap-form-header.tsx
│ │ │ ├── swap-form.tsx
│ │ │ ├── swap-settings.tsx
│ │ │ └── swap-simulation.tsx
│ │ ├── constants.ts
│ │ ├── hooks
│ │ │ ├── swap-simulation-query.ts
│ │ │ ├── swap-status-notifications.ts
│ │ │ └── swap-status-query.ts
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ └── providers
│ │ │ ├── swap-form.tsx
│ │ │ ├── swap-settings.tsx
│ │ │ └── swap-transaction.tsx
│ └── vault
│ │ ├── actions
│ │ ├── build-vault-withdrawal-fee-tx.ts
│ │ └── get-wallet-vaults.ts
│ │ ├── components
│ │ ├── claim-fee-button.tsx
│ │ ├── vault-claim-params-form.tsx
│ │ └── vault-info.tsx
│ │ ├── hooks
│ │ └── use-wallet-vaults-query.ts
│ │ ├── page.tsx
│ │ └── providers
│ │ ├── index.ts
│ │ └── vault-claim-params.tsx
│ ├── components.json
│ ├── components
│ ├── address-input.tsx
│ ├── asset-select.tsx
│ ├── header.tsx
│ ├── nav-bar.tsx
│ ├── ui
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── command.tsx
│ │ ├── dialog.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── popover.tsx
│ │ ├── skeleton.tsx
│ │ ├── toast.tsx
│ │ └── toaster.tsx
│ └── wallet-guard.tsx
│ ├── constants.ts
│ ├── hooks
│ ├── use-assets-query.ts
│ ├── use-blockchain-explorer.ts
│ ├── use-pool-query.ts
│ ├── use-routers.ts
│ ├── use-ston-api.ts
│ └── use-toast.ts
│ ├── lib
│ ├── promise.ts
│ ├── routers-repository.ts
│ ├── ston-api-client.ts
│ ├── ton-api-client.ts
│ └── utils.ts
│ ├── next.config.mjs
│ ├── package.json
│ ├── postcss.config.mjs
│ ├── public
│ ├── icons
│ │ ├── gitbook.svg
│ │ └── github.svg
│ └── tonconnect-manifest.json
│ ├── ts-reset.d.ts
│ └── tsconfig.json
├── lefthook.yml
├── package.json
├── packages
├── sdk
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── client
│ │ │ ├── Client.test.ts
│ │ │ ├── Client.ts
│ │ │ └── index.ts
│ │ ├── contracts
│ │ │ ├── core
│ │ │ │ ├── Contract.ts
│ │ │ │ ├── JettonMinter.ts
│ │ │ │ ├── JettonWallet.ts
│ │ │ │ └── constants.ts
│ │ │ ├── dex
│ │ │ │ ├── constants.ts
│ │ │ │ ├── errors.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── v1
│ │ │ │ │ ├── LpAccountV1.test.ts
│ │ │ │ │ ├── LpAccountV1.ts
│ │ │ │ │ ├── PoolV1.test.ts
│ │ │ │ │ ├── PoolV1.ts
│ │ │ │ │ ├── RouterV1.test.ts
│ │ │ │ │ ├── RouterV1.ts
│ │ │ │ │ ├── constants.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── v2_1
│ │ │ │ │ ├── LpAccount
│ │ │ │ │ │ ├── LpAccountV2_1.test.ts
│ │ │ │ │ │ └── LpAccountV2_1.ts
│ │ │ │ │ ├── constants.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── pool
│ │ │ │ │ │ ├── BasePoolV2_1.test.ts
│ │ │ │ │ │ ├── BasePoolV2_1.ts
│ │ │ │ │ │ ├── CPIPoolV2_1.test.ts
│ │ │ │ │ │ ├── CPIPoolV2_1.ts
│ │ │ │ │ │ ├── StablePoolV2_1.test.ts
│ │ │ │ │ │ ├── StablePoolV2_1.ts
│ │ │ │ │ │ ├── WCPIPoolV2_1.test.ts
│ │ │ │ │ │ ├── WCPIPoolV2_1.ts
│ │ │ │ │ │ ├── WStablePoolV2_1.test.ts
│ │ │ │ │ │ └── WStablePoolV2_1.ts
│ │ │ │ │ ├── router
│ │ │ │ │ │ ├── BaseRouterV2_1.test.ts
│ │ │ │ │ │ ├── BaseRouterV2_1.ts
│ │ │ │ │ │ ├── CPIRouterV2_1.test.ts
│ │ │ │ │ │ ├── CPIRouterV2_1.ts
│ │ │ │ │ │ ├── StableRouterV2_1.test.ts
│ │ │ │ │ │ ├── StableRouterV2_1.ts
│ │ │ │ │ │ ├── WCPIRouterV2_1.test.ts
│ │ │ │ │ │ ├── WCPIRouterV2_1.ts
│ │ │ │ │ │ ├── WStableRouterV2_1.test.ts
│ │ │ │ │ │ └── WStableRouterV2_1.ts
│ │ │ │ │ └── vault
│ │ │ │ │ │ ├── VaultV2_1.test.ts
│ │ │ │ │ │ └── VaultV2_1.ts
│ │ │ │ └── v2_2
│ │ │ │ │ ├── LpAccount
│ │ │ │ │ ├── LpAccountV2_2.test.ts
│ │ │ │ │ └── LpAccountV2_2.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── pool
│ │ │ │ │ ├── BasePoolV2_2.test.ts
│ │ │ │ │ ├── BasePoolV2_2.ts
│ │ │ │ │ ├── CPIPoolV2_2.test.ts
│ │ │ │ │ ├── CPIPoolV2_2.ts
│ │ │ │ │ ├── StablePoolV2_2.test.ts
│ │ │ │ │ ├── StablePoolV2_2.ts
│ │ │ │ │ ├── WCPIPoolV2_2.test.ts
│ │ │ │ │ ├── WCPIPoolV2_2.ts
│ │ │ │ │ ├── WStablePoolV2_2.test.ts
│ │ │ │ │ └── WStablePoolV2_2.ts
│ │ │ │ │ ├── router
│ │ │ │ │ ├── BaseRouterV2_2.test.ts
│ │ │ │ │ ├── BaseRouterV2_2.ts
│ │ │ │ │ ├── CPIRouterV2_2.test.ts
│ │ │ │ │ ├── CPIRouterV2_2.ts
│ │ │ │ │ ├── StableRouterV2_2.test.ts
│ │ │ │ │ ├── StableRouterV2_2.ts
│ │ │ │ │ ├── WCPIRouterV2_2.test.ts
│ │ │ │ │ ├── WCPIRouterV2_2.ts
│ │ │ │ │ ├── WStableRouterV2_2.test.ts
│ │ │ │ │ └── WStableRouterV2_2.ts
│ │ │ │ │ └── vault
│ │ │ │ │ ├── VaultV2_2.test.ts
│ │ │ │ │ └── VaultV2_2.ts
│ │ │ ├── farm
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── v1
│ │ │ │ │ ├── FarmNftItemV1.test.ts
│ │ │ │ │ ├── FarmNftItemV1.ts
│ │ │ │ │ ├── FarmNftMinterV1.test.ts
│ │ │ │ │ ├── FarmNftMinterV1.ts
│ │ │ │ │ └── index.ts
│ │ │ │ ├── v2
│ │ │ │ │ ├── FarmNftItemV2.test.ts
│ │ │ │ │ ├── FarmNftItemV2.ts
│ │ │ │ │ ├── FarmNftMinterV2.test.ts
│ │ │ │ │ ├── FarmNftMinterV2.ts
│ │ │ │ │ └── index.ts
│ │ │ │ └── v3
│ │ │ │ │ ├── FarmNftItemV3.test.ts
│ │ │ │ │ ├── FarmNftItemV3.ts
│ │ │ │ │ ├── FarmNftMinterV3.test.ts
│ │ │ │ │ ├── FarmNftMinterV3.ts
│ │ │ │ │ └── index.ts
│ │ │ └── pTON
│ │ │ │ ├── AbstractPton.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── v1
│ │ │ │ ├── PtonV1.test.ts
│ │ │ │ ├── PtonV1.ts
│ │ │ │ └── constants.ts
│ │ │ │ └── v2_1
│ │ │ │ ├── PtonV2_1.test.ts
│ │ │ │ ├── PtonV2_1.ts
│ │ │ │ └── constants.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── test-utils
│ │ │ ├── createMockObj.ts
│ │ │ ├── index.ts
│ │ │ └── snapshot.ts
│ │ ├── types.ts
│ │ └── utils
│ │ │ ├── createJettonTransferMessage.test.ts
│ │ │ ├── createJettonTransferMessage.ts
│ │ │ ├── createSbtDestroyMessage.test.ts
│ │ │ ├── createSbtDestroyMessage.ts
│ │ │ ├── toAddress.test.ts
│ │ │ └── toAddress.ts
│ ├── tsconfig.json
│ └── tsup.config.ts
└── typescript-config
│ ├── package.json
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 |
9 | # Runtime data
10 | node_modules
11 | dist
12 | coverage
13 | *.local
14 | packages/*/build-report.html
15 |
16 | # Editor directories and files
17 | .vscode
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | .husky
27 | .turbo
28 | .vercel
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 STON.fi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ./packages/sdk/README.md
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3 | "files": {
4 | "ignoreUnknown": true,
5 | "include": ["./examples/**", "./packages/**"],
6 | "ignore": ["node_modules", ".turbo", ".next", "dist", "coverage"]
7 | },
8 | "organizeImports": {
9 | "enabled": true
10 | },
11 | "formatter": {
12 | "enabled": true,
13 | "indentStyle": "space"
14 | },
15 | "linter": {
16 | "enabled": true,
17 | "rules": {
18 | "complexity": {
19 | "noStaticOnlyClass": "off"
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/examples/next-js-app/.dockerignore:
--------------------------------------------------------------------------------
1 | Dockerfile
2 | .dockerignore
3 | node_modules
4 | npm-debug.log
5 | README.md
6 | .next
7 | .turbo
8 | .git
9 |
--------------------------------------------------------------------------------
/examples/next-js-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/core-web-vitals", "next/typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/examples/next-js-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 |
32 | # env files (can opt-in for committing if needed)
33 | .env*
34 |
35 | # vercel
36 | .vercel
37 |
38 | # typescript
39 | *.tsbuildinfo
40 | next-env.d.ts
41 |
--------------------------------------------------------------------------------
/examples/next-js-app/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22-alpine AS base
2 |
3 | FROM base AS pnpm-base
4 |
5 | ENV PNPM_HOME="/pnpm"
6 | ENV PATH="$PNPM_HOME:$PATH"
7 |
8 | RUN corepack enable
9 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install turbo --global
10 |
11 |
12 | FROM pnpm-base AS builder
13 | # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
14 | RUN apk add --no-cache libc6-compat
15 | RUN apk update
16 |
17 | WORKDIR /app
18 |
19 | COPY . .
20 |
21 | ENV TURBO_TELEMETRY_DISABLED=1
22 | RUN turbo prune @ston-fi/sdk-example-next-js-app --docker
23 |
24 |
25 | FROM pnpm-base AS installer
26 |
27 | RUN apk add --no-cache libc6-compat
28 | RUN apk update
29 |
30 | WORKDIR /app
31 |
32 | # First install dependencies (as they change less often)
33 | COPY .gitignore .gitignore
34 | COPY --from=builder /app/out/json/ .
35 | COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
36 | COPY --from=builder /app/out/pnpm-workspace.yaml ./pnpm-workspace.yaml
37 |
38 | # Enable pnpm and install deps
39 | RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --ignore-scripts --frozen-lockfile
40 |
41 | # Build the project and its dependencies
42 | COPY --from=builder /app/out/full/ .
43 | COPY turbo.json ./turbo.json
44 |
45 | # Uncomment and use build args to enable remote caching
46 | # ARG TURBO_API
47 | # ENV TURBO_API=$TURBO_API
48 |
49 | # ARG TURBO_TEAM
50 | # ENV TURBO_TEAM=$TURBO_TEAM
51 |
52 | # ARG TURBO_TOKEN
53 | # ENV TURBO_TOKEN=$TURBO_TOKEN
54 |
55 | ENV TURBO_TELEMETRY_DISABLED=1
56 | RUN turbo build --filter=@ston-fi/sdk-example-next-js-app
57 |
58 |
59 | FROM base AS runner
60 |
61 | # dumb-init registers signal handlers for every signal that can be caught
62 | RUN apk update && apk add --no-cache dumb-init
63 |
64 | WORKDIR /app
65 |
66 | # Don't run production as root
67 | RUN addgroup --system --gid 1001 nodejs
68 | RUN adduser --system --uid 1001 app
69 | USER app
70 |
71 | COPY --from=installer --chown=app:nodejs /app/examples/next-js-app/next.config.mjs .
72 | COPY --from=installer --chown=app:nodejs /app/examples/next-js-app/package.json .
73 |
74 | # Automatically leverage output traces to reduce image size
75 | # https://nextjs.org/docs/advanced-features/output-file-tracing
76 | COPY --from=installer --chown=app:nodejs /app/examples/next-js-app/.next/standalone ./
77 | COPY --from=installer --chown=app:nodejs /app/examples/next-js-app/.next/static ./examples/next-js-app/.next/static
78 | COPY --from=installer --chown=app:nodejs /app/examples/next-js-app/public ./examples/next-js-app/public
79 |
80 | ARG PORT=8080
81 | ENV NODE_ENV=production
82 |
83 | EXPOSE ${PORT}
84 |
85 | ENTRYPOINT ["dumb-init"]
86 |
87 | CMD ["node", "--enable-source-maps", "./examples/next-js-app/server.js", "--port", "${PORT}"]
88 |
--------------------------------------------------------------------------------
/examples/next-js-app/README.md:
--------------------------------------------------------------------------------
1 | # SDK Next.js demo app
2 |
3 | This is an demo app to demonstrate the SDK package usage in real life and provide code as a docs for those who prefer this way.
4 |
5 | You can try the demo app [here](https://sdk-demo-app.ston.fi) or run it locally by following these steps:
6 |
7 | 1. install dependencies
8 |
9 | ```sh
10 | pnpm install
11 | ```
12 |
13 | 2. run the dev command
14 |
15 | ```sh
16 | turbo dev
17 | ```
18 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/globals.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @custom-variant dark (&:is(.dark *));
4 |
5 | @theme {
6 | --color-border: hsl(var(--border));
7 | --color-input: hsl(var(--input));
8 | --color-ring: hsl(var(--ring));
9 | --color-background: hsl(var(--background));
10 | --color-foreground: hsl(var(--foreground));
11 |
12 | --color-primary: hsl(var(--primary));
13 | --color-primary-foreground: hsl(var(--primary-foreground));
14 |
15 | --color-secondary: hsl(var(--secondary));
16 | --color-secondary-foreground: hsl(var(--secondary-foreground));
17 |
18 | --color-destructive: hsl(var(--destructive));
19 | --color-destructive-foreground: hsl(var(--destructive-foreground));
20 |
21 | --color-muted: hsl(var(--muted));
22 | --color-muted-foreground: hsl(var(--muted-foreground));
23 |
24 | --color-accent: hsl(var(--accent));
25 | --color-accent-foreground: hsl(var(--accent-foreground));
26 |
27 | --color-popover: hsl(var(--popover));
28 | --color-popover-foreground: hsl(var(--popover-foreground));
29 |
30 | --color-card: hsl(var(--card));
31 | --color-card-foreground: hsl(var(--card-foreground));
32 |
33 | --radius-lg: var(--radius);
34 | --radius-md: calc(var(--radius) - 2px);
35 | --radius-sm: calc(var(--radius) - 4px);
36 |
37 | --animate-accordion-down: accordion-down 0.2s ease-out;
38 | --animate-accordion-up: accordion-up 0.2s ease-out;
39 |
40 | @keyframes accordion-down {
41 | from {
42 | height: 0;
43 | }
44 | to {
45 | height: var(--radix-accordion-content-height);
46 | }
47 | }
48 | @keyframes accordion-up {
49 | from {
50 | height: var(--radix-accordion-content-height);
51 | }
52 | to {
53 | height: 0;
54 | }
55 | }
56 | }
57 |
58 | @utility container {
59 | margin-inline: auto;
60 | padding-inline: 2rem;
61 | @media (width >= --theme(--breakpoint-sm)) {
62 | max-width: none;
63 | }
64 | @media (width >= 1400px) {
65 | max-width: 1400px;
66 | }
67 | }
68 |
69 | /*
70 | The default border color has changed to `currentColor` in Tailwind CSS v4,
71 | so we've added these compatibility styles to make sure everything still
72 | looks the same as it did with Tailwind CSS v3.
73 |
74 | If we ever want to remove these styles, we need to add an explicit border
75 | color utility to any element that depends on these defaults.
76 | */
77 | @layer base {
78 | *,
79 | ::after,
80 | ::before,
81 | ::backdrop,
82 | ::file-selector-button {
83 | border-color: var(--color-gray-200, currentColor);
84 | }
85 | }
86 |
87 | @layer base {
88 | :root {
89 | --test: 0 100% 50%;
90 |
91 | --background: 0 0% 100%;
92 | --foreground: 240 4% 5%;
93 |
94 | --card: var(--background);
95 | --card-foreground: var(--foreground);
96 |
97 | --popover: var(--background);
98 | --popover-foreground: var(--foreground);
99 |
100 | --primary: 210 100% 50%;
101 | --primary-foreground: 0 0% 100%;
102 |
103 | --secondary: 210 40% 96.1%;
104 | --secondary-foreground: var(--foreground);
105 |
106 | --muted: 210 40% 96.1%;
107 | --muted-foreground: var(--foreground);
108 |
109 | --accent: 210 40% 96.1%;
110 | --accent-foreground: var(--foreground);
111 |
112 | --destructive: 353 80% 56%;
113 | --destructive-foreground: 0 0% 100%;
114 |
115 | --border: 214.3 31.8% 91.4%;
116 | --input: 214.3 31.8% 91.4%;
117 | --ring: 222.2 84% 4.9%;
118 |
119 | --radius: 0.5rem;
120 | }
121 | }
122 |
123 | @layer base {
124 | * {
125 | @apply border-border;
126 | }
127 | body {
128 | @apply bg-background text-foreground;
129 | }
130 | }
131 |
132 | button[data-tc-connect-button] {
133 | @apply bg-primary transform-none!;
134 | }
135 |
136 | button[data-tc-dropdown-button] {
137 | @apply shadow-md transform-none!;
138 | }
139 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { unstable_noStore as noStore } from "next/cache";
3 | import { Inter } from "next/font/google";
4 |
5 | import { Header } from "@/components/header";
6 | import { NavBar } from "@/components/nav-bar";
7 | import { Toaster } from "@/components/ui/toaster";
8 | import { ROUTES } from "@/constants";
9 | import { cn } from "@/lib/utils";
10 |
11 | import { Providers } from "./providers";
12 | import "./globals.css";
13 |
14 | const inter = Inter({ subsets: ["latin"] });
15 |
16 | const navBarLinks = [
17 | { href: ROUTES.swap, label: "Swap" },
18 | { href: ROUTES.vault, label: "Vault" },
19 | ];
20 |
21 | export const metadata: Metadata = {
22 | title: "Ston.fi SDK Example app",
23 | };
24 |
25 | export default function RootLayout({
26 | children,
27 | }: {
28 | children: React.ReactNode;
29 | }) {
30 | noStore();
31 |
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 | {children}
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { redirect } from "next/navigation";
2 |
3 | export default async function Home() {
4 | redirect("/swap");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/providers.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
4 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
5 | import { THEME, TonConnectUIProvider } from "@tonconnect/ui-react";
6 | import type React from "react";
7 |
8 | export const queryClient = new QueryClient();
9 |
10 | function QueryProvider({ children }: { children: React.ReactNode }) {
11 | return (
12 |
13 | {children}
14 |
15 |
16 | );
17 | }
18 |
19 | function TonConnectProvider({ children }: { children: React.ReactNode }) {
20 | return (
21 |
28 | {children}
29 |
30 | );
31 | }
32 |
33 | export function Providers({ children }: { children: React.ReactNode }) {
34 | return (
35 |
36 | {children}
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/actions/build-swap-transaction.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import type { SwapSimulation } from "@ston-fi/api";
4 | import {
5 | type AddressType,
6 | type AmountType,
7 | type QueryIdType,
8 | dexFactory,
9 | } from "@ston-fi/sdk";
10 | import type { SendTransactionRequest } from "@tonconnect/ui-react";
11 |
12 | import { getRouter } from "@/lib/routers-repository";
13 | import { tonApiClient } from "@/lib/ton-api-client";
14 |
15 | import { TON_ADDRESS } from "@/constants";
16 |
17 | const getSwapTxParams = async (
18 | simulation: SwapSimulation,
19 | walletAddress: string,
20 | params?: {
21 | queryId?: QueryIdType;
22 | referralAddress?: AddressType;
23 | referralValue?: AmountType;
24 | },
25 | ) => {
26 | const routerMetadata = await getRouter(simulation.routerAddress);
27 |
28 | if (!routerMetadata) {
29 | throw new Error(`Router ${simulation.routerAddress} not found`);
30 | }
31 |
32 | const dexContracts = dexFactory(routerMetadata);
33 |
34 | const router = tonApiClient.open(
35 | dexContracts.Router.create(routerMetadata.address),
36 | );
37 |
38 | const sharedTxParams = {
39 | ...params,
40 | userWalletAddress: walletAddress,
41 | offerAmount: simulation.offerUnits,
42 | minAskAmount: simulation.minAskUnits,
43 | };
44 |
45 | if (
46 | simulation.askAddress !== TON_ADDRESS &&
47 | simulation.offerAddress !== TON_ADDRESS
48 | ) {
49 | return router.getSwapJettonToJettonTxParams({
50 | ...sharedTxParams,
51 | offerJettonAddress: simulation.offerAddress,
52 | askJettonAddress: simulation.askAddress,
53 | });
54 | }
55 |
56 | const proxyTon = dexContracts.pTON.create(routerMetadata.ptonMasterAddress);
57 |
58 | if (simulation.offerAddress === TON_ADDRESS) {
59 | return router.getSwapTonToJettonTxParams({
60 | ...sharedTxParams,
61 | proxyTon,
62 | askJettonAddress: simulation.askAddress,
63 | });
64 | }
65 |
66 | return router.getSwapJettonToTonTxParams({
67 | ...sharedTxParams,
68 | proxyTon,
69 | offerJettonAddress: simulation.offerAddress,
70 | });
71 | };
72 |
73 | export const buildSwapTransaction = async (
74 | simulation: SwapSimulation,
75 | walletAddress: string,
76 | params?: {
77 | queryId?: QueryIdType;
78 | referralAddress?: AddressType;
79 | referralValue?: AmountType;
80 | },
81 | ) => {
82 | const txParams = await getSwapTxParams(simulation, walletAddress, params);
83 |
84 | const messages: SendTransactionRequest["messages"] = [
85 | {
86 | address: txParams.to.toString(),
87 | amount: txParams.value.toString(),
88 | payload: txParams.body?.toBoc().toString("base64"),
89 | },
90 | ];
91 |
92 | return messages;
93 | };
94 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/components/referral-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type React from "react";
4 | import { useId, useState } from "react";
5 |
6 | import { Card, CardContent } from "@/components/ui/card";
7 | import { Input } from "@/components/ui/input";
8 | import { useRouters } from "@/hooks/use-routers";
9 | import {
10 | cn,
11 | isValidAddress,
12 | percentToBps,
13 | validateFloatValue,
14 | } from "@/lib/utils";
15 | import {
16 | DEFAULT_REFERRAL_VALUE_PERCENT,
17 | MAX_REFERRAL_VALUE_PERCENT,
18 | MIN_REFERRAL_VALUE_PERCENT,
19 | } from "../constants";
20 | import { useSwapSimulation } from "../hooks/swap-simulation-query";
21 | import { useSwapForm, useSwapFormDispatch } from "../providers/swap-form";
22 |
23 | export const ReferralForm = () => {
24 | return (
25 |
26 |
27 |
31 |
32 |
33 |
34 | );
35 | };
36 |
37 | const ReferralAddressInput: React.FC<
38 | Omit, "children">
39 | > = (props) => {
40 | const id = useId();
41 | const dispatch = useSwapFormDispatch();
42 | const [isValid, setIsValid] = useState(true);
43 |
44 | const handleChange: React.ChangeEventHandler = ({
45 | target,
46 | }) => {
47 | const isValidTonAddress = isValidAddress(target.value);
48 | dispatch({
49 | type: "SET_REFERRAL_ADDRESS",
50 | payload: isValidTonAddress ? target.value : undefined,
51 | });
52 | setIsValid(!target.value || isValidTonAddress);
53 | };
54 |
55 | return (
56 |
60 |
61 | Referral address:
62 |
63 |
72 |
73 | );
74 | };
75 |
76 | const validateReferralValue = (value: string) => {
77 | const valueAsNumber = Number.parseFloat(value);
78 |
79 | if (Number.isNaN(valueAsNumber)) {
80 | return false;
81 | }
82 |
83 | return (
84 | valueAsNumber >= MIN_REFERRAL_VALUE_PERCENT &&
85 | valueAsNumber <= MAX_REFERRAL_VALUE_PERCENT
86 | );
87 | };
88 |
89 | const ReferralValueInput: React.FC<
90 | Omit, "children">
91 | > = (props) => {
92 | const id = useId();
93 | const { referralAddress } = useSwapForm();
94 | const [referralValue, setReferralValue] = useState("");
95 | const dispatch = useSwapFormDispatch();
96 | const isValidReferralValue =
97 | !referralValue || validateReferralValue(referralValue);
98 |
99 | const handleChange: React.ChangeEventHandler = ({
100 | target,
101 | }) => {
102 | if (target.value && !validateFloatValue(target.value, 2)) return;
103 |
104 | setReferralValue(target.value);
105 | dispatch({
106 | type: "SET_REFERRAL_VALUE",
107 | payload: validateReferralValue(target.value)
108 | ? percentToBps(Number.parseFloat(target.value) / 100)
109 | : undefined,
110 | });
111 | };
112 |
113 | return (
114 |
118 |
119 | Referral percentage:
120 |
121 |
132 | {!isValidReferralValue && (
133 |
134 | Invalid referral percentage (default {DEFAULT_REFERRAL_VALUE_PERCENT}%
135 | will be used)
136 |
137 | )}
138 |
139 | );
140 | };
141 |
142 | const ReferralValueDisclaimer = () => {
143 | const { data: routers } = useRouters();
144 | const { data: swapSimulation } = useSwapSimulation();
145 | const { referralValue, referralAddress } = useSwapForm();
146 | const router = swapSimulation
147 | ? routers?.get(swapSimulation.routerAddress)
148 | : null;
149 |
150 | if (!referralValue || !referralAddress || router?.majorVersion !== 1) {
151 | return null;
152 | }
153 |
154 | return (
155 |
156 | Custom referral value cannot be applied since swap will be performed via
157 | v1 contracts (default {DEFAULT_REFERRAL_VALUE_PERCENT}% will be used)
158 |
159 | );
160 | };
161 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/components/swap-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useTonAddress, useTonConnectUI } from "@tonconnect/ui-react";
4 | import { useState } from "react";
5 |
6 | import { Button } from "@/components/ui/button";
7 | import { useToast } from "@/hooks/use-toast";
8 |
9 | import { buildSwapTransaction } from "../actions/build-swap-transaction";
10 | import { useSwapSimulation } from "../hooks/swap-simulation-query";
11 | import { useSwapStatusNotifications } from "../hooks/swap-status-notifications";
12 | import { useSwapStatusQuery } from "../hooks/swap-status-query";
13 | import { useSwapForm } from "../providers/swap-form";
14 | import { useSetSwapTransactionDetails } from "../providers/swap-transaction";
15 |
16 | export function SwapButton() {
17 | const walletAddress = useTonAddress();
18 | const [tonConnectUI] = useTonConnectUI();
19 | const {
20 | offerAmount,
21 | offerAsset,
22 | askAsset,
23 | askAmount,
24 | referralValue,
25 | referralAddress,
26 | } = useSwapForm();
27 | const swapSimulationQuery = useSwapSimulation();
28 | const setSwapTransaction = useSetSwapTransactionDetails();
29 | const swapStatusQuery = useSwapStatusQuery();
30 | const [isClicked, setIsClicked] = useState(false);
31 | const { toast } = useToast();
32 |
33 | useSwapStatusNotifications();
34 |
35 | const handleSwap = async () => {
36 | if (!swapSimulationQuery.data || !walletAddress) {
37 | return;
38 | }
39 |
40 | try {
41 | const queryId = Date.now();
42 | setIsClicked(true);
43 | const messages = await buildSwapTransaction(
44 | swapSimulationQuery.data,
45 | walletAddress,
46 | {
47 | queryId,
48 | referralAddress,
49 | referralValue,
50 | },
51 | );
52 |
53 | await tonConnectUI.sendTransaction({
54 | validUntil: Date.now() + 1000000,
55 | messages,
56 | });
57 | toast({ title: "Transaction sent to the network" });
58 | setSwapTransaction({
59 | queryId,
60 | ownerAddress: walletAddress,
61 | routerAddress: swapSimulationQuery.data.routerAddress,
62 | });
63 | } catch {
64 | setSwapTransaction(null);
65 | } finally {
66 | setIsClicked(false);
67 | }
68 | };
69 |
70 | if (!walletAddress) {
71 | return (
72 | tonConnectUI.openModal()}>
73 | Connect wallet
74 |
75 | );
76 | }
77 |
78 | if (!offerAsset || !askAsset) {
79 | return (
80 |
81 | Select an asset
82 |
83 | );
84 | }
85 |
86 | if (!offerAmount && !askAmount) {
87 | return (
88 |
89 | Enter an amount
90 |
91 | );
92 | }
93 |
94 | if (swapSimulationQuery.isLoading) {
95 | return (
96 |
97 | ...
98 |
99 | );
100 | }
101 |
102 | if (!swapSimulationQuery.data) {
103 | return Invalid swap ;
104 | }
105 |
106 | return (
107 |
116 | Swap
117 |
118 | );
119 | }
120 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/components/swap-form-header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { RefreshCw, Settings } from "lucide-react";
4 |
5 | import { Button } from "@/components/ui/button";
6 |
7 | import { useSwapSimulation } from "../hooks/swap-simulation-query";
8 |
9 | import { SwapSettings } from "./swap-settings";
10 |
11 | export const SwapFormHeader = () => {
12 | const swapSimulationQuery = useSwapSimulation();
13 |
14 | return (
15 |
16 |
Swap
17 |
18 | swapSimulationQuery.refetch()}
25 | >
26 |
30 |
31 |
34 |
35 |
36 | }
37 | />
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/components/swap-settings.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useId } from "react";
4 |
5 | import { Button } from "@/components/ui/button";
6 | import {
7 | Dialog,
8 | DialogContent,
9 | DialogHeader,
10 | DialogTitle,
11 | DialogTrigger,
12 | } from "@/components/ui/dialog";
13 | import { Input } from "@/components/ui/input";
14 | import { Label } from "@/components/ui/label";
15 |
16 | import {
17 | SLIPPAGE_TOLERANCE_OPTIONS,
18 | useSwapSettings,
19 | } from "../providers/swap-settings";
20 |
21 | export function SwapSettings({
22 | trigger = (
23 |
24 | Settings
25 |
26 | ),
27 | }: {
28 | trigger?: React.ReactNode;
29 | }) {
30 | return (
31 |
32 | {trigger}
33 |
34 |
35 | Swap Settings
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | const transformValue = (value: number) => value * 100;
44 | const transformValueBack = (value: number) => value / 100;
45 |
46 | const SlippageToleranceSection = () => {
47 | const { slippageTolerance, setSlippageTolerance } = useSwapSettings();
48 |
49 | const inputId = useId();
50 |
51 | return (
52 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/components/swap-simulation.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ArrowRightLeft } from "lucide-react";
4 | import { useState } from "react";
5 |
6 | import { Skeleton } from "@/components/ui/skeleton";
7 | import { bigNumberToFloat, cn } from "@/lib/utils";
8 |
9 | import {
10 | type SwapSimulation,
11 | useSwapSimulation,
12 | } from "../hooks/swap-simulation-query";
13 | import { useSwapForm } from "../providers/swap-form";
14 |
15 | export const SwapSimulationPreview = (props: { className?: string }) => {
16 | const { data, error, isFetching, isFetched } = useSwapSimulation();
17 |
18 | if (!isFetched && !isFetching) {
19 | return null;
20 | }
21 |
22 | return (
23 |
24 | {error ? (
25 |
26 | ) : data ? (
27 |
28 | ) : (
29 |
30 | )}
31 |
32 | );
33 | };
34 |
35 | const SwapSimulationError = ({ error }: { error: Error }) => {
36 | return (
37 |
38 | Error:
39 | {error.message}
40 |
41 | );
42 | };
43 |
44 | const SwapSimulationData = ({ data }: { data: SwapSimulation }) => {
45 | const { askAsset, offerAsset } = useSwapForm();
46 | const [swapRateDirection, setSwapRateDirection] = useState<
47 | "forward" | "reverse"
48 | >("forward");
49 |
50 | if (!askAsset || !offerAsset) {
51 | return null;
52 | }
53 |
54 | return (
55 |
109 | );
110 | };
111 |
112 | const SwapSimulationLoading = () => {
113 | return (
114 |
115 | {Array.from({ length: 6 }, (_, i) => i).map((i) => (
116 |
117 | ))}
118 |
119 | );
120 | };
121 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_REFERRAL_VALUE_PERCENT = 0.1;
2 |
3 | export const MIN_REFERRAL_VALUE_PERCENT = 0.01;
4 | export const MAX_REFERRAL_VALUE_PERCENT = 1;
5 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/hooks/swap-simulation-query.ts:
--------------------------------------------------------------------------------
1 | import type { SwapSimulation } from "@ston-fi/api";
2 | import {
3 | type UseQueryOptions,
4 | skipToken,
5 | useQuery,
6 | } from "@tanstack/react-query";
7 | import { useTonAddress } from "@tonconnect/ui-react";
8 |
9 | import { useStonApi } from "@/hooks/use-ston-api";
10 | import { floatToBigNumber } from "@/lib/utils";
11 |
12 | import { useSwapForm } from "../providers/swap-form";
13 | import { useSwapSettings } from "../providers/swap-settings";
14 | import { useSwapStatusQuery } from "./swap-status-query";
15 |
16 | export type { SwapSimulation };
17 |
18 | export const SWAP_SIMULATION_QUERY_KEY = "swap-simulation";
19 |
20 | export function useSwapSimulation(
21 | options?: Omit, "queryKey" | "queryFn">,
22 | ) {
23 | const swapFormState = useSwapForm();
24 | const stonApi = useStonApi();
25 | const { slippageTolerance } = useSwapSettings();
26 | const swapStatusQuery = useSwapStatusQuery();
27 |
28 | const walletAddress = useTonAddress();
29 |
30 | return useQuery({
31 | refetchInterval: 30 * 1000, // update every 30 seconds
32 | ...options,
33 | queryKey: [
34 | SWAP_SIMULATION_QUERY_KEY,
35 | swapFormState,
36 | walletAddress,
37 | slippageTolerance,
38 | ],
39 | queryFn:
40 | !swapStatusQuery.isFetching &&
41 | swapFormState.askAsset &&
42 | swapFormState.offerAsset &&
43 | (swapFormState.askAmount || swapFormState.offerAmount)
44 | ? async () => {
45 | const {
46 | askAsset,
47 | offerAsset,
48 | askAmount,
49 | offerAmount,
50 | referralAddress,
51 | referralValue,
52 | } = swapFormState;
53 |
54 | const shared = {
55 | referralAddress,
56 | referralFeeBps:
57 | referralAddress && referralValue
58 | ? referralValue.toString()
59 | : undefined,
60 | slippageTolerance: (slippageTolerance / 100).toString(),
61 | dexV2: true,
62 | } as const;
63 |
64 | if (offerAsset && offerAmount && askAsset) {
65 | return stonApi.simulateSwap({
66 | ...shared,
67 | offerAddress: offerAsset.contractAddress,
68 | offerUnits: floatToBigNumber(
69 | offerAmount,
70 | offerAsset.meta?.decimals ?? 9,
71 | ).toString(),
72 | askAddress: askAsset.contractAddress,
73 | });
74 | }
75 |
76 | if (offerAsset && askAsset && askAmount) {
77 | return stonApi.simulateReverseSwap({
78 | ...shared,
79 | offerAddress: offerAsset.contractAddress,
80 | askAddress: askAsset.contractAddress,
81 | askUnits: floatToBigNumber(
82 | askAmount,
83 | askAsset.meta?.decimals ?? 9,
84 | ).toString(),
85 | });
86 | }
87 |
88 | throw new Error("Invalid swap form state.");
89 | }
90 | : skipToken,
91 | });
92 | }
93 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/hooks/swap-status-notifications.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 |
3 | import { useToast } from "@/hooks/use-toast";
4 |
5 | import { useSwapStatusQuery } from "./swap-status-query";
6 |
7 | export const useSwapStatusNotifications = () => {
8 | const { toast } = useToast();
9 | const { data, isError } = useSwapStatusQuery();
10 |
11 | useEffect(() => {
12 | if (!isError) return;
13 |
14 | toast({ title: "Transaction status refetch has been failed!" });
15 | }, [isError, toast]);
16 |
17 | useEffect(() => {
18 | if (!data?.exitCode) return;
19 |
20 | toast({
21 | title:
22 | data.exitCode === "failed"
23 | ? "Transaction failed"
24 | : data.exitCode === "swap_ok"
25 | ? "Transaction has successfully finished"
26 | : "Transaction has finished with unknown status",
27 | });
28 | }, [data?.exitCode, toast]);
29 | };
30 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/hooks/swap-status-query.ts:
--------------------------------------------------------------------------------
1 | import type { SwapStatus } from "@ston-fi/api";
2 | import {
3 | type UseQueryOptions,
4 | skipToken,
5 | useQuery,
6 | } from "@tanstack/react-query";
7 |
8 | import { useStonApi } from "@/hooks/use-ston-api";
9 | import { AbortedPromiseError, promiseWithSignal, sleep } from "@/lib/promise";
10 |
11 | import { useSwapTransactionDetails } from "../providers/swap-transaction";
12 |
13 | export const SWAP_STATUS_QUERY_KEY = "swap-status";
14 |
15 | const SWAP_STATUS_REFETCH_INTERVAL_MS = 5_000; // 5s
16 | const SWAP_STATUS_TIMEOUT_MS = 30_0000; // 5m
17 |
18 | export const useSwapStatusQuery = (
19 | options?: Omit, "queryKey" | "queryFn">,
20 | ) => {
21 | const stonApi = useStonApi();
22 | const transactionDetails = useSwapTransactionDetails();
23 |
24 | return useQuery({
25 | ...options,
26 | queryKey: [SWAP_STATUS_QUERY_KEY, transactionDetails?.queryId],
27 | queryFn: transactionDetails
28 | ? async () => {
29 | const signal = AbortSignal.timeout(SWAP_STATUS_TIMEOUT_MS);
30 |
31 | let swapStatus: SwapStatus;
32 |
33 | do {
34 | swapStatus = await promiseWithSignal(
35 | stonApi.getSwapStatus({
36 | ...transactionDetails,
37 | queryId: transactionDetails.queryId.toString(),
38 | }),
39 | signal,
40 | );
41 |
42 | if (swapStatus["@type"] === "Found") {
43 | break;
44 | }
45 |
46 | await sleep(SWAP_STATUS_REFETCH_INTERVAL_MS);
47 | } while (swapStatus["@type"] === "NotFound");
48 |
49 | return swapStatus;
50 | }
51 | : skipToken,
52 | retry(failureCount, error) {
53 | if (error instanceof AbortedPromiseError) {
54 | return false;
55 | }
56 |
57 | return failureCount <= 3;
58 | },
59 | staleTime: Number.POSITIVE_INFINITY,
60 | select: (data) => (data["@type"] === "NotFound" ? null : data),
61 | });
62 | };
63 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/layout.tsx:
--------------------------------------------------------------------------------
1 | import { SwapFormProvider } from "./providers/swap-form";
2 | import { SwapSettingsProvider } from "./providers/swap-settings";
3 | import { SwapTransactionProvider } from "./providers/swap-transaction";
4 |
5 | export default function Layout({
6 | children,
7 | }: Readonly<{
8 | children: React.ReactNode;
9 | }>) {
10 | return (
11 |
12 |
13 | {children}
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/page.tsx:
--------------------------------------------------------------------------------
1 | import { ReferralForm } from "./components/referral-form";
2 | import { SwapButton } from "./components/swap-button";
3 | import { SwapForm } from "./components/swap-form";
4 | import { SwapFormHeader } from "./components/swap-form-header";
5 | import { SwapSimulationPreview } from "./components/swap-simulation";
6 |
7 | export default function Home() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/providers/swap-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | type Dispatch,
5 | type ReactNode,
6 | createContext,
7 | useContext,
8 | useReducer,
9 | } from "react";
10 |
11 | import type { AssetInfo } from "@/hooks/use-assets-query";
12 |
13 | type SwapState = {
14 | offerAsset: AssetInfo | null;
15 | askAsset: AssetInfo | null;
16 | offerAmount: string;
17 | askAmount: string;
18 | referralAddress?: string;
19 | referralValue?: number;
20 | };
21 |
22 | const initialState: SwapState = {
23 | offerAsset: null,
24 | askAsset: null,
25 | offerAmount: "",
26 | askAmount: "",
27 | };
28 |
29 | type IAction =
30 | | {
31 | type: "SET_OFFER_ASSET" | "SET_ASK_ASSET";
32 | payload: AssetInfo | null;
33 | }
34 | | {
35 | type: "SET_OFFER_AMOUNT" | "SET_ASK_AMOUNT";
36 | payload: string;
37 | }
38 | | { type: "SET_REFERRAL_ADDRESS"; payload: string | undefined }
39 | | { type: "SET_REFERRAL_VALUE"; payload: number | undefined };
40 |
41 | const SwapContext = createContext(initialState);
42 | const SwapContextDispatch = createContext>(() => {});
43 |
44 | const swapReducer = (state: SwapState, action: IAction): SwapState => {
45 | if (action.type === "SET_OFFER_ASSET") {
46 | const shouldResetAsk =
47 | state.askAsset?.contractAddress === action.payload?.contractAddress;
48 |
49 | return {
50 | ...state,
51 | offerAsset: action.payload,
52 | askAsset: shouldResetAsk ? null : state.askAsset,
53 | askAmount: shouldResetAsk ? "" : state.askAmount,
54 | };
55 | }
56 |
57 | if (action.type === "SET_ASK_ASSET") {
58 | return { ...state, askAsset: action.payload };
59 | }
60 |
61 | if (action.type === "SET_OFFER_AMOUNT") {
62 | return { ...state, offerAmount: action.payload, askAmount: "" };
63 | }
64 |
65 | if (action.type === "SET_ASK_AMOUNT") {
66 | return { ...state, askAmount: action.payload, offerAmount: "" };
67 | }
68 |
69 | if (action.type === "SET_REFERRAL_ADDRESS") {
70 | return { ...state, referralAddress: action.payload };
71 | }
72 |
73 | if (action.type === "SET_REFERRAL_VALUE") {
74 | return { ...state, referralValue: action.payload };
75 | }
76 |
77 | return state;
78 | };
79 |
80 | export const SwapFormProvider = ({ children }: { children: ReactNode }) => {
81 | const [state, dispatch] = useReducer(swapReducer, initialState);
82 |
83 | return (
84 |
85 |
86 | {children}
87 |
88 |
89 | );
90 | };
91 |
92 | export const useSwapForm = () => useContext(SwapContext);
93 | export const useSwapFormDispatch = () => useContext(SwapContextDispatch);
94 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/providers/swap-settings.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { createContext, useContext, useState } from "react";
4 |
5 | type SwapSettings = {
6 | slippageTolerance: number;
7 | setSlippageTolerance: (value: SwapSettings["slippageTolerance"]) => void;
8 | };
9 |
10 | export const SLIPPAGE_TOLERANCE_OPTIONS = [0.005, 0.01, 0.05] as const;
11 |
12 | const DEFAULT_SLIPPAGE_TOLERANCE: SwapSettings["slippageTolerance"] =
13 | SLIPPAGE_TOLERANCE_OPTIONS[1];
14 |
15 | const SwapSettingsContext = createContext({} as SwapSettings);
16 |
17 | export const SwapSettingsProvider = ({
18 | children,
19 | }: {
20 | children: React.ReactNode;
21 | }) => {
22 | const [slippageTolerance, setSlippageTolerance] = useState(
23 | DEFAULT_SLIPPAGE_TOLERANCE,
24 | );
25 |
26 | return (
27 |
33 | {children}
34 |
35 | );
36 | };
37 |
38 | export const useSwapSettings = () => {
39 | const context = useContext(SwapSettingsContext);
40 |
41 | if (!context) {
42 | throw new Error(
43 | "useSwapSettings must be used within a SwapSettingsProvider",
44 | );
45 | }
46 |
47 | return context;
48 | };
49 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/swap/providers/swap-transaction.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { QueryIdType } from "@ston-fi/sdk";
4 | import { createContext, useContext, useState } from "react";
5 |
6 | export interface ITransactionDetails {
7 | queryId: QueryIdType;
8 | routerAddress: string;
9 | ownerAddress: string;
10 | }
11 |
12 | const SwapTransactionContext = createContext(null);
13 | const SetSwapTransactionContext = createContext<
14 | React.Dispatch>
15 | >(() => {});
16 |
17 | export const useSwapTransactionDetails = () =>
18 | useContext(SwapTransactionContext);
19 | export const useSetSwapTransactionDetails = () =>
20 | useContext(SetSwapTransactionContext);
21 |
22 | export const SwapTransactionProvider = ({
23 | children,
24 | }: {
25 | children: React.ReactNode;
26 | }) => {
27 | const [transaction, setTransaction] = useState(
28 | null,
29 | );
30 |
31 | return (
32 |
33 |
34 | {children}
35 |
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/actions/build-vault-withdrawal-fee-tx.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { DEX_VERSION, routerFactory } from "@ston-fi/sdk";
4 | import type { SendTransactionRequest } from "@tonconnect/ui-react";
5 |
6 | import { TON_ADDRESS } from "@/constants";
7 | import { getRouter } from "@/lib/routers-repository";
8 | import { tonApiClient } from "@/lib/ton-api-client";
9 |
10 | type GetVaultParams = {
11 | userWalletAddress: string;
12 | routerAddress: string;
13 | tokenMinter: string;
14 | };
15 |
16 | const getVault = async ({
17 | userWalletAddress,
18 | routerAddress,
19 | tokenMinter,
20 | }: GetVaultParams) => {
21 | const routerInfo = await getRouter(routerAddress);
22 |
23 | if (!routerInfo) {
24 | throw new Error("Unknown router");
25 | }
26 |
27 | const router = tonApiClient.open(routerFactory(routerInfo));
28 |
29 | if (!("getVault" in router)) {
30 | throw new Error(`Vault contract does not exist in DEX ${DEX_VERSION.v1}`);
31 | }
32 |
33 | return router.getVault({
34 | tokenMinter:
35 | tokenMinter === TON_ADDRESS ? routerInfo.ptonMasterAddress : tokenMinter,
36 | user: userWalletAddress,
37 | });
38 | };
39 |
40 | export async function buildVaultWithdrawalFeeTx(
41 | params: GetVaultParams[],
42 | ): Promise {
43 | const vaults = (await Promise.all(params.map(getVault))).map((vault) =>
44 | tonApiClient.open(vault),
45 | );
46 |
47 | const txParams = await Promise.all(
48 | vaults.map((vault) => vault.getWithdrawFeeTxParams()),
49 | );
50 |
51 | return txParams.map(({ to, value, body }) => ({
52 | address: to.toString(),
53 | amount: value.toString(),
54 | payload: body?.toBoc().toString("base64"),
55 | }));
56 | }
57 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/actions/get-wallet-vaults.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { stonApiClient } from "@/lib/ston-api-client";
4 |
5 | export async function getWalletVaults(params: { userWalletAddress: string }) {
6 | const vaultsData = await stonApiClient.getWalletVaultsFee({
7 | walletAddress: params.userWalletAddress,
8 | });
9 |
10 | return vaultsData.map((data) => ({
11 | vaultAddress: data.vaultAddress,
12 | ownerAddress: params.userWalletAddress,
13 | tokenAddress: data.assetAddress,
14 | routerAddress: data.routerAddress,
15 | depositedAmount: data.balance,
16 | }));
17 | }
18 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/components/claim-fee-button.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 |
3 | import { Button, type ButtonProps } from "@/components/ui/button";
4 | import { useTonConnectUI } from "@tonconnect/ui-react";
5 |
6 | import { buildVaultWithdrawalFeeTx } from "../actions/build-vault-withdrawal-fee-tx";
7 | import { useVaultClaimParams } from "../providers/vault-claim-params";
8 |
9 | export const ClaimWithdrawalFeeButton: React.FC<
10 | ButtonProps & { routerAddress: string; tokenMinters: string[] }
11 | > = ({ routerAddress, tokenMinters, ...props }) => {
12 | const { walletAddress: userWalletAddress } = useVaultClaimParams();
13 |
14 | const [tonConnectUI] = useTonConnectUI();
15 | const [isLoading, setIsLoading] = useState(false);
16 |
17 | const handleClaim: ButtonProps["onClick"] = async (event) => {
18 | if (props.onClick) {
19 | props.onClick(event);
20 | }
21 |
22 | setIsLoading(true);
23 |
24 | try {
25 | const withdrawalFeeTxParams = await buildVaultWithdrawalFeeTx(
26 | tokenMinters.map((tokenMinter) => ({
27 | routerAddress,
28 | userWalletAddress,
29 | tokenMinter,
30 | })),
31 | );
32 |
33 | await tonConnectUI.sendTransaction({
34 | validUntil: Date.now() + 1000000,
35 | messages: withdrawalFeeTxParams,
36 | });
37 | } finally {
38 | setIsLoading(false);
39 | }
40 | };
41 |
42 | return (
43 |
48 | );
49 | };
50 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/components/vault-claim-params-form.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { AddressInput } from "@/components/address-input";
4 | import { cn } from "@/lib/utils";
5 |
6 | import { useVaultClaimParams, useVaultClaimParamsDispatch } from "../providers";
7 |
8 | export const VaultClaimParamsForm: React.FC<
9 | Omit, "children">
10 | > = (props) => {
11 | const { walletAddress } = useVaultClaimParams();
12 | const dispatch = useVaultClaimParamsDispatch();
13 |
14 | return (
15 |
16 |
21 | dispatch({ type: "SET_WALLET_ADDRESS", payload: address })
22 | }
23 | />
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/components/vault-info.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type React from "react";
4 |
5 | import { Card, CardContent } from "@/components/ui/card";
6 | import { useAssetsQuery } from "@/hooks/use-assets-query";
7 |
8 | import { useBlockchainExplorer } from "@/hooks/use-blockchain-explorer";
9 | import { bigNumberToFloat, cn } from "@/lib/utils";
10 |
11 | import { useWalletVaultsQuery } from "../hooks/use-wallet-vaults-query";
12 | import { useVaultClaimParams } from "../providers";
13 |
14 | import { ClaimWithdrawalFeeButton } from "./claim-fee-button";
15 |
16 | const VaultInfo: React.FC<
17 | Omit, "children"> & {
18 | vaultAddress: string;
19 | routerAddress: string;
20 | tokenMinter: string;
21 | depositedAmount: string;
22 | }
23 | > = ({
24 | vaultAddress,
25 | routerAddress,
26 | tokenMinter,
27 | depositedAmount,
28 | ...props
29 | }) => {
30 | const { data: assetInfo } = useAssetsQuery({
31 | select: (data) =>
32 | data.find(({ contractAddress }) => contractAddress === tokenMinter),
33 | });
34 |
35 | const blockchainExplorer = useBlockchainExplorer();
36 |
37 | return (
38 |
39 |
40 |
65 |
66 |
71 | Claim
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | export const WalletVaultsInfo: React.FC<
79 | Omit, "children">
80 | > = () => {
81 | const { walletAddress } = useVaultClaimParams();
82 | const walletVaultsQuery = useWalletVaultsQuery();
83 |
84 | if (!walletAddress) {
85 | return null;
86 | }
87 |
88 | if (!walletVaultsQuery.data) {
89 | return Fetching vaults...
;
90 | }
91 |
92 | return (
93 | <>
94 |
95 | {walletVaultsQuery.data.map((vault) => (
96 |
103 | ))}
104 |
105 | >
106 | );
107 | };
108 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/hooks/use-wallet-vaults-query.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | type UseQueryResult,
5 | skipToken,
6 | useQuery,
7 | } from "@tanstack/react-query";
8 |
9 | import { getWalletVaults } from "../actions/get-wallet-vaults";
10 | import { useVaultClaimParams } from "../providers";
11 |
12 | export const VAULT_QUERY_KEY = "vault";
13 |
14 | type VaultData = Awaited>;
15 |
16 | export const useWalletVaultsQuery = (
17 | options?: Omit, "queryKey" | "queryFn">,
18 | ) => {
19 | const { walletAddress: userWalletAddress } = useVaultClaimParams();
20 |
21 | return useQuery({
22 | ...options,
23 | queryKey: [VAULT_QUERY_KEY, userWalletAddress],
24 | queryFn: userWalletAddress
25 | ? () => getWalletVaults({ userWalletAddress })
26 | : skipToken,
27 | });
28 | };
29 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Card } from "@/components/ui/card";
4 | import { WalletGuard } from "@/components/wallet-guard";
5 |
6 | import { VaultClaimParamsForm } from "./components/vault-claim-params-form";
7 | import { WalletVaultsInfo } from "./components/vault-info";
8 | import { VaultClaimParamsProvider } from "./providers";
9 |
10 | export default function VaultPage() {
11 | return (
12 |
13 |
26 |
27 | }>
28 |
29 |
30 |
31 | );
32 | }
33 |
34 | function VaultPageContentWithWallet() {
35 | return (
36 |
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | function VaultPageContentWithoutWallet() {
44 | return (
45 |
46 | Please connect your wallet first
47 |
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/providers/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./vault-claim-params";
2 |
--------------------------------------------------------------------------------
/examples/next-js-app/app/vault/providers/vault-claim-params.tsx:
--------------------------------------------------------------------------------
1 | import { useTonAddress } from "@tonconnect/ui-react";
2 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
3 | import { createContext, useContext, useEffect, useReducer } from "react";
4 |
5 | const URL_PARAMS = {
6 | WALLET_ADDRESS: "ref_address",
7 | } as const;
8 |
9 | type VaultClaimParams = {
10 | walletAddress: string;
11 | };
12 |
13 | type Action = {
14 | type: "SET_WALLET_ADDRESS";
15 | payload: VaultClaimParams["walletAddress"];
16 | };
17 |
18 | const VaultClaimParamsContext = createContext({
19 | walletAddress: "",
20 | });
21 |
22 | const VaultClaimParamsContextDispatch = createContext>(
23 | () => {
24 | throw new Error(
25 | "Cannot use VaultClaimParamsDispatch outside of VaultClaimParamsProvider",
26 | );
27 | },
28 | );
29 |
30 | const vaultClaimParamsReducer = (
31 | state: VaultClaimParams,
32 | action: Action,
33 | ): VaultClaimParams => {
34 | if (action.type === "SET_WALLET_ADDRESS") {
35 | return { ...state, walletAddress: action.payload };
36 | }
37 |
38 | return state;
39 | };
40 |
41 | export const VaultClaimParamsProvider = ({
42 | children,
43 | }: { children: React.ReactNode }) => {
44 | const searchParams = useSearchParams();
45 | const router = useRouter();
46 | const pathname = usePathname();
47 | const walletAddress = useTonAddress();
48 |
49 | const [state, dispatch] = useReducer(vaultClaimParamsReducer, {
50 | walletAddress:
51 | searchParams.get(URL_PARAMS.WALLET_ADDRESS) ?? walletAddress ?? "",
52 | });
53 |
54 | useEffect(() => {
55 | const params = new URLSearchParams();
56 | if (state.walletAddress)
57 | params.set(URL_PARAMS.WALLET_ADDRESS, state.walletAddress);
58 |
59 | const search = params.toString();
60 | const newUrl = `${pathname}${search ? `?${search}` : ""}`;
61 |
62 | router.replace(newUrl, { scroll: false });
63 | }, [state, pathname, router]);
64 |
65 | return (
66 |
67 |
68 | {children}
69 |
70 |
71 | );
72 | };
73 |
74 | export const useVaultClaimParams = () => {
75 | const context = useContext(VaultClaimParamsContext);
76 |
77 | if (context === undefined) {
78 | throw new Error(
79 | "useVaultClaimParams must be used within a VaultClaimParamsProvider",
80 | );
81 | }
82 |
83 | return context;
84 | };
85 |
86 | export const useVaultClaimParamsDispatch = () => {
87 | const dispatch = useContext(VaultClaimParamsContextDispatch);
88 |
89 | if (dispatch === undefined) {
90 | throw new Error(
91 | "useVaultClaimParamsDispatch must be used within a VaultClaimParamsProvider",
92 | );
93 | }
94 |
95 | return dispatch;
96 | };
97 |
--------------------------------------------------------------------------------
/examples/next-js-app/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/address-input.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useCallback, useId, useMemo, useState } from "react";
4 |
5 | import { Input } from "@/components/ui/input";
6 | import { TonAddressRegex } from "@/constants";
7 | import { cn } from "@/lib/utils";
8 |
9 | function validateAddress(address: unknown, options: { required?: boolean }) {
10 | if (options.required && !address) {
11 | return new Error("Address is required");
12 | }
13 |
14 | if (!options.required && !address) {
15 | return null;
16 | }
17 |
18 | if (typeof address !== "string") {
19 | return new Error("Address should be a string");
20 | }
21 |
22 | if (!TonAddressRegex.test(address)) {
23 | return new Error("Invalid address");
24 | }
25 |
26 | return null;
27 | }
28 |
29 | interface AddressInputProps
30 | extends Omit<
31 | React.ComponentPropsWithoutRef,
32 | "value" | "onChange"
33 | > {
34 | label?: React.ReactNode;
35 | address: string;
36 | onAddressChange: (address: string) => void;
37 | }
38 |
39 | export function AddressInput({
40 | label,
41 | address,
42 | onAddressChange,
43 | required,
44 | ...props
45 | }: AddressInputProps) {
46 | const fallbackId = useId();
47 | const inputId = props.id ?? fallbackId;
48 |
49 | const validationOptions = useMemo(() => ({ required }), [required]);
50 |
51 | const [inputValue, setInputValue] = useState(address);
52 | const [validationError, setValidationError] = useState(
53 | validateAddress(address, validationOptions),
54 | );
55 |
56 | const handleChange = useCallback>(
57 | (e) => {
58 | const value = e.target.value;
59 | setInputValue(value);
60 |
61 | const error = validateAddress(value, validationOptions);
62 | setValidationError(error);
63 | onAddressChange(!error ? value : "");
64 | },
65 | [onAddressChange, validationOptions],
66 | );
67 |
68 | return (
69 |
70 | {label ?
{label} : null}
71 |
72 |
85 |
86 | {validationError ? (
87 |
88 | {validationError.message}
89 |
90 | ) : null}
91 |
92 | );
93 | }
94 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/asset-select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { ChevronDown } from "lucide-react";
4 | import { useState } from "react";
5 |
6 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
7 | import { Button } from "@/components/ui/button";
8 | import {
9 | Command,
10 | CommandEmpty,
11 | CommandGroup,
12 | CommandInput,
13 | CommandItem,
14 | CommandList,
15 | } from "@/components/ui/command";
16 | import {
17 | Popover,
18 | PopoverContent,
19 | PopoverTrigger,
20 | } from "@/components/ui/popover";
21 | import { Skeleton } from "@/components/ui/skeleton";
22 | import type { AssetInfo } from "@/hooks/use-assets-query";
23 | import { bigNumberToFloat, cn } from "@/lib/utils";
24 |
25 | type AssetSelectProps = {
26 | assets?: AssetInfo[];
27 | selectedAsset: AssetInfo | null;
28 | onAssetSelect?: (asset: AssetInfo | null) => void;
29 | className?: string;
30 | loading?: boolean;
31 | };
32 |
33 | export function AssetSelect({
34 | assets = [],
35 | selectedAsset,
36 | onAssetSelect,
37 | loading,
38 | className,
39 | }: AssetSelectProps) {
40 | const [open, setOpen] = useState(false);
41 |
42 | const handleAssetSelect = (assetAddress: string) => {
43 | const asset = assets.find(
44 | (asset) => asset.contractAddress === assetAddress,
45 | );
46 |
47 | if (asset && onAssetSelect) {
48 | onAssetSelect(asset);
49 | }
50 |
51 | setOpen(false);
52 | };
53 |
54 | const handleFilter = (_: string, search: string, keywords: string[] = []) => {
55 | const [symbol = ""] = keywords;
56 | return symbol.toLowerCase().includes(search.toLowerCase()) ? 1 : 0;
57 | };
58 |
59 | if (loading) {
60 | return ;
61 | }
62 |
63 | return (
64 |
65 |
66 |
71 | {selectedAsset ? (
72 | <>
73 |
74 |
81 |
82 | {selectedAsset.meta?.symbol}
83 | >
84 | ) : (
85 | "Select asset..."
86 | )}
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | No asset found.
95 |
96 | {assets.map((asset) => (
97 |
104 |
105 |
109 |
110 |
111 |
112 |
113 | {asset.meta?.symbol}
114 |
115 | {asset.balance ? (
116 |
117 | {bigNumberToFloat(
118 | asset.balance,
119 | asset.meta?.decimals ?? 9,
120 | )}
121 |
122 | ) : null}
123 |
124 | ))}
125 |
126 |
127 |
128 |
129 |
130 | );
131 | }
132 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/header.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Image from "next/image";
4 |
5 | import { TonConnectButton } from "@tonconnect/ui-react";
6 |
7 | import { Badge } from "@/components/ui/badge";
8 | import GitBookIcon from "@/public/icons/gitbook.svg";
9 | import GitHubIcon from "@/public/icons/github.svg";
10 |
11 | export function Header() {
12 | return (
13 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/nav-bar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import { usePathname } from "next/navigation";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const NavBarLink: React.FC> = (
9 | props,
10 | ) => {
11 | const pathname = usePathname();
12 |
13 | return (
14 |
22 | );
23 | };
24 |
25 | export const NavBar: React.FC<
26 | Omit, "children"> & {
27 | links: Array<{ href: string; label: string }>;
28 | }
29 | > = ({ links, ...props }) => {
30 | return (
31 |
32 |
33 | {links.map(({ label, href }) => (
34 |
35 | {label}
36 |
37 | ))}
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as AvatarPrimitive from "@radix-ui/react-avatar";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ));
21 | Avatar.displayName = AvatarPrimitive.Root.displayName;
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ));
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ));
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
49 |
50 | export { Avatar, AvatarImage, AvatarFallback };
51 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import { type VariantProps, cva } from "class-variance-authority";
2 | import type * as React from "react";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | },
24 | );
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | );
34 | }
35 |
36 | export { Badge, badgeVariants };
37 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { type VariantProps, cva } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | },
34 | );
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean;
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button";
45 | return (
46 |
51 | );
52 | },
53 | );
54 | Button.displayName = "Button";
55 |
56 | export { Button, buttonVariants };
57 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ));
18 | Card.displayName = "Card";
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ));
30 | CardHeader.displayName = "CardHeader";
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ));
45 | CardTitle.displayName = "CardTitle";
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLDivElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ));
57 | CardDescription.displayName = "CardDescription";
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ));
65 | CardContent.displayName = "CardContent";
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ));
77 | CardFooter.displayName = "CardFooter";
78 |
79 | export {
80 | Card,
81 | CardHeader,
82 | CardFooter,
83 | CardTitle,
84 | CardDescription,
85 | CardContent,
86 | };
87 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as DialogPrimitive from "@radix-ui/react-dialog";
4 | import { X } from "lucide-react";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Dialog = DialogPrimitive.Root;
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger;
12 |
13 | const DialogPortal = DialogPrimitive.Portal;
14 |
15 | const DialogClose = DialogPrimitive.Close;
16 |
17 | const DialogOverlay = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef
20 | >(({ className, ...props }, ref) => (
21 |
29 | ));
30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
31 |
32 | const DialogContent = React.forwardRef<
33 | React.ElementRef,
34 | React.ComponentPropsWithoutRef
35 | >(({ className, children, ...props }, ref) => (
36 |
37 |
38 |
46 | {children}
47 |
48 |
49 | Close
50 |
51 |
52 |
53 | ));
54 | DialogContent.displayName = DialogPrimitive.Content.displayName;
55 |
56 | const DialogHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
67 | );
68 | DialogHeader.displayName = "DialogHeader";
69 |
70 | const DialogFooter = ({
71 | className,
72 | ...props
73 | }: React.HTMLAttributes) => (
74 |
81 | );
82 | DialogFooter.displayName = "DialogFooter";
83 |
84 | const DialogTitle = React.forwardRef<
85 | React.ElementRef,
86 | React.ComponentPropsWithoutRef
87 | >(({ className, ...props }, ref) => (
88 |
96 | ));
97 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
98 |
99 | const DialogDescription = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ));
109 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
110 |
111 | export {
112 | Dialog,
113 | DialogPortal,
114 | DialogOverlay,
115 | DialogClose,
116 | DialogTrigger,
117 | DialogContent,
118 | DialogHeader,
119 | DialogFooter,
120 | DialogTitle,
121 | DialogDescription,
122 | };
123 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | );
18 | },
19 | );
20 | Input.displayName = "Input";
21 |
22 | export { Input };
23 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as LabelPrimitive from "@radix-ui/react-label";
4 | import { type VariantProps, cva } from "class-variance-authority";
5 | import * as React from "react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11 | );
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ));
24 | Label.displayName = LabelPrimitive.Root.displayName;
25 |
26 | export { Label };
27 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as PopoverPrimitive from "@radix-ui/react-popover";
4 | import * as React from "react";
5 |
6 | import { cn } from "@/lib/utils";
7 |
8 | const Popover = PopoverPrimitive.Root;
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ));
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
30 |
31 | export { Popover, PopoverTrigger, PopoverContent };
32 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils";
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast";
11 | import { useToast } from "@/hooks/use-toast";
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast();
15 |
16 | return (
17 |
18 | {toasts.map(({ id, title, description, action, ...props }) => (
19 |
20 |
21 | {title && {title} }
22 | {description && {description} }
23 |
24 | {action}
25 |
26 |
27 | ))}
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/examples/next-js-app/components/wallet-guard.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useTonAddress } from "@tonconnect/ui-react";
4 | import type React from "react";
5 |
6 | export const WalletGuard: React.FC<
7 | React.PropsWithChildren<{ fallback?: React.ReactNode }>
8 | > = ({ children, fallback = null }) => {
9 | const walletAddress = useTonAddress();
10 |
11 | if (!walletAddress) {
12 | return fallback;
13 | }
14 |
15 | return children;
16 | };
17 |
--------------------------------------------------------------------------------
/examples/next-js-app/constants.ts:
--------------------------------------------------------------------------------
1 | export const TON_ADDRESS = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c";
2 |
3 | export const TonAddressRegex =
4 | /(^((EQ|UQ)[a-zA-Z0-9-_]{46})$)|(^((-1|0):[a-zA-Z0-9]{64})$)/;
5 |
6 | export const ROUTES = {
7 | swap: "/swap",
8 | vault: "/vault",
9 | } as const;
10 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-assets-query.ts:
--------------------------------------------------------------------------------
1 | import { type AssetInfoV2 as AssetInfo, AssetTag } from "@ston-fi/api";
2 | import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
3 | import { useIsConnectionRestored, useTonAddress } from "@tonconnect/ui-react";
4 |
5 | import { useStonApi } from "./use-ston-api";
6 |
7 | export const ASSETS_QUERY_KEY = "assets";
8 |
9 | export type { AssetInfo };
10 |
11 | export const useAssetsQuery = (
12 | options?: Omit<
13 | UseQueryOptions,
14 | "queryKey" | "queryFn"
15 | >,
16 | ) => {
17 | const isConnectionRestored = useIsConnectionRestored();
18 | const walletAddress = useTonAddress();
19 |
20 | const client = useStonApi();
21 |
22 | return useQuery({
23 | ...options,
24 | queryKey: [ASSETS_QUERY_KEY, walletAddress],
25 | enabled: isConnectionRestored,
26 | queryFn: async () => {
27 | const assets = await client.queryAssets({
28 | condition: [
29 | AssetTag.LiquidityVeryHigh,
30 | AssetTag.LiquidityHigh,
31 | AssetTag.LiquidityMedium,
32 | AssetTag.WalletHasBalance,
33 | ].join(" | "),
34 | walletAddress,
35 | });
36 |
37 | return assets.sort((a, b) => {
38 | if (a.popularityIndex && b.popularityIndex) {
39 | return b.popularityIndex - a.popularityIndex;
40 | }
41 |
42 | if (a.popularityIndex && !b.popularityIndex) return -1;
43 | if (!a.popularityIndex && b.popularityIndex) return 1;
44 |
45 | return 0;
46 | });
47 | },
48 | });
49 | };
50 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-blockchain-explorer.ts:
--------------------------------------------------------------------------------
1 | const BLOCKCHAIN_EXPLORER_BAE_URL = "https://tonviewer.com/";
2 |
3 | function contract(address: string) {
4 | return `${BLOCKCHAIN_EXPLORER_BAE_URL}/${address}`;
5 | }
6 |
7 | function transaction(hash: string) {
8 | return `${BLOCKCHAIN_EXPLORER_BAE_URL}/transaction/${hash}`;
9 | }
10 |
11 | const blockchainExplorer = {
12 | contract,
13 | transaction,
14 | };
15 |
16 | export function useBlockchainExplorer() {
17 | return blockchainExplorer;
18 | }
19 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-pool-query.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import type { PoolInfo } from "@ston-fi/api";
4 | import {
5 | type UseQueryOptions,
6 | skipToken,
7 | useQuery,
8 | } from "@tanstack/react-query";
9 |
10 | import { isValidAddress } from "@/lib/utils";
11 |
12 | import { useStonApi } from "./use-ston-api";
13 |
14 | export const POOL_QUERY_KEY = "pool";
15 |
16 | export const usePoolQuery = (
17 | poolAddress: string,
18 | options?: Omit<
19 | UseQueryOptions,
20 | "queryKey" | "queryFn"
21 | >,
22 | ) => {
23 | const client = useStonApi();
24 |
25 | return useQuery({
26 | ...options,
27 | queryKey: [POOL_QUERY_KEY, poolAddress],
28 | queryFn:
29 | poolAddress && isValidAddress(poolAddress)
30 | ? () => client.getPool(poolAddress)
31 | : skipToken,
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-routers.ts:
--------------------------------------------------------------------------------
1 | import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
2 |
3 | import { type RouterInfo, getRouters } from "@/lib/routers-repository";
4 |
5 | export const useRouters = (
6 | options?: Omit<
7 | UseQueryOptions>,
8 | "queryKey" | "queryFn"
9 | >,
10 | ) =>
11 | useQuery({
12 | ...options,
13 | queryKey: ["get-routers"],
14 | queryFn: async () => {
15 | const routers = await getRouters();
16 |
17 | return new Map(routers.map((router) => [router.address, router]));
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-ston-api.ts:
--------------------------------------------------------------------------------
1 | import { stonApiClient } from "@/lib/ston-api-client";
2 |
3 | export const useStonApi = () => stonApiClient;
4 |
--------------------------------------------------------------------------------
/examples/next-js-app/hooks/use-toast.ts:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | // Inspired by react-hot-toast library
4 | import * as React from "react";
5 |
6 | import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
7 |
8 | const TOAST_LIMIT = 1;
9 | const TOAST_REMOVE_DELAY = 1000000;
10 |
11 | type ToasterToast = ToastProps & {
12 | id: string;
13 | title?: React.ReactNode;
14 | description?: React.ReactNode;
15 | action?: ToastActionElement;
16 | };
17 |
18 | const actionTypes = {
19 | ADD_TOAST: "ADD_TOAST",
20 | UPDATE_TOAST: "UPDATE_TOAST",
21 | DISMISS_TOAST: "DISMISS_TOAST",
22 | REMOVE_TOAST: "REMOVE_TOAST",
23 | } as const;
24 |
25 | let count = 0;
26 |
27 | function genId() {
28 | count = (count + 1) % Number.MAX_SAFE_INTEGER;
29 | return count.toString();
30 | }
31 |
32 | type ActionType = typeof actionTypes;
33 |
34 | type Action =
35 | | {
36 | type: ActionType["ADD_TOAST"];
37 | toast: ToasterToast;
38 | }
39 | | {
40 | type: ActionType["UPDATE_TOAST"];
41 | toast: Partial;
42 | }
43 | | {
44 | type: ActionType["DISMISS_TOAST"];
45 | toastId?: ToasterToast["id"];
46 | }
47 | | {
48 | type: ActionType["REMOVE_TOAST"];
49 | toastId?: ToasterToast["id"];
50 | };
51 |
52 | interface State {
53 | toasts: ToasterToast[];
54 | }
55 |
56 | const toastTimeouts = new Map>();
57 |
58 | const addToRemoveQueue = (toastId: string) => {
59 | if (toastTimeouts.has(toastId)) {
60 | return;
61 | }
62 |
63 | const timeout = setTimeout(() => {
64 | toastTimeouts.delete(toastId);
65 | dispatch({
66 | type: "REMOVE_TOAST",
67 | toastId: toastId,
68 | });
69 | }, TOAST_REMOVE_DELAY);
70 |
71 | toastTimeouts.set(toastId, timeout);
72 | };
73 |
74 | export const reducer = (state: State, action: Action): State => {
75 | switch (action.type) {
76 | case "ADD_TOAST":
77 | return {
78 | ...state,
79 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
80 | };
81 |
82 | case "UPDATE_TOAST":
83 | return {
84 | ...state,
85 | toasts: state.toasts.map((t) =>
86 | t.id === action.toast.id ? { ...t, ...action.toast } : t,
87 | ),
88 | };
89 |
90 | case "DISMISS_TOAST": {
91 | const { toastId } = action;
92 |
93 | // ! Side effects ! - This could be extracted into a dismissToast() action,
94 | // but I'll keep it here for simplicity
95 | if (toastId) {
96 | addToRemoveQueue(toastId);
97 | } else {
98 | // biome-ignore lint/complexity/noForEach: shadcn impl
99 | state.toasts.forEach((toast) => {
100 | addToRemoveQueue(toast.id);
101 | });
102 | }
103 |
104 | return {
105 | ...state,
106 | toasts: state.toasts.map((t) =>
107 | t.id === toastId || toastId === undefined
108 | ? {
109 | ...t,
110 | open: false,
111 | }
112 | : t,
113 | ),
114 | };
115 | }
116 | case "REMOVE_TOAST":
117 | if (action.toastId === undefined) {
118 | return {
119 | ...state,
120 | toasts: [],
121 | };
122 | }
123 | return {
124 | ...state,
125 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
126 | };
127 | }
128 | };
129 |
130 | const listeners: Array<(state: State) => void> = [];
131 |
132 | let memoryState: State = { toasts: [] };
133 |
134 | function dispatch(action: Action) {
135 | memoryState = reducer(memoryState, action);
136 | // biome-ignore lint/complexity/noForEach: shadcn impl
137 | listeners.forEach((listener) => {
138 | listener(memoryState);
139 | });
140 | }
141 |
142 | type Toast = Omit;
143 |
144 | function toast({ ...props }: Toast) {
145 | const id = genId();
146 |
147 | const update = (props: ToasterToast) =>
148 | dispatch({
149 | type: "UPDATE_TOAST",
150 | toast: { ...props, id },
151 | });
152 | const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
153 |
154 | dispatch({
155 | type: "ADD_TOAST",
156 | toast: {
157 | ...props,
158 | id,
159 | open: true,
160 | onOpenChange: (open) => {
161 | if (!open) dismiss();
162 | },
163 | },
164 | });
165 |
166 | return {
167 | id: id,
168 | dismiss,
169 | update,
170 | };
171 | }
172 |
173 | function useToast() {
174 | const [state, setState] = React.useState(memoryState);
175 |
176 | // biome-ignore lint/correctness/useExhaustiveDependencies: shadcn impl
177 | React.useEffect(() => {
178 | listeners.push(setState);
179 | return () => {
180 | const index = listeners.indexOf(setState);
181 | if (index > -1) {
182 | listeners.splice(index, 1);
183 | }
184 | };
185 | }, [state]);
186 |
187 | return {
188 | ...state,
189 | toast,
190 | dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
191 | };
192 | }
193 |
194 | export { useToast, toast };
195 |
--------------------------------------------------------------------------------
/examples/next-js-app/lib/promise.ts:
--------------------------------------------------------------------------------
1 | export class AbortedPromiseError extends Error {
2 | constructor(message?: string, options?: ErrorOptions) {
3 | super(message, options);
4 | this.name = "AbortedPromiseError";
5 | }
6 | }
7 |
8 | export const sleep = (ms: number) =>
9 | new Promise((resolve) => setTimeout(resolve, ms));
10 |
11 | export const promiseWithSignal = async (
12 | promise: Promise,
13 | signal: AbortSignal,
14 | reason = new AbortedPromiseError("Aborted"),
15 | ) => {
16 | if (signal.aborted) {
17 | throw reason;
18 | }
19 |
20 | const result = await Promise.race([
21 | promise,
22 | new Promise((_, reject) => {
23 | signal.addEventListener("abort", () => reject(reason));
24 | }),
25 | ]);
26 |
27 | return result;
28 | };
29 |
--------------------------------------------------------------------------------
/examples/next-js-app/lib/routers-repository.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import type { RouterInfo } from "@ston-fi/api";
4 |
5 | import { stonApiClient } from "./ston-api-client";
6 |
7 | const routersCache: Map = new Map();
8 |
9 | export type { RouterInfo };
10 |
11 | export const getRouter = async (routerAddress: string) => {
12 | try {
13 | if (!routersCache.size) {
14 | await getRouters();
15 | }
16 |
17 | const routerFromCache = routersCache.get(routerAddress);
18 |
19 | if (routerFromCache) return routerFromCache;
20 |
21 | const router = await stonApiClient.getRouter(routerAddress);
22 |
23 | routersCache.set(router.address, router);
24 |
25 | return router;
26 | } catch {
27 | return null;
28 | }
29 | };
30 |
31 | export const getRouters = async () => {
32 | try {
33 | if (!routersCache.size) {
34 | const routers = await stonApiClient.getRouters();
35 |
36 | for (const router of routers) {
37 | routersCache.set(router.address, router);
38 | }
39 | }
40 |
41 | return [...routersCache.values()];
42 | } catch {
43 | return [];
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/examples/next-js-app/lib/ston-api-client.ts:
--------------------------------------------------------------------------------
1 | import { StonApiClient } from "@ston-fi/api";
2 |
3 | export const stonApiClient = new StonApiClient({
4 | baseURL: process.env.STON_API_URL ?? "https://api.ston.fi",
5 | });
6 |
--------------------------------------------------------------------------------
/examples/next-js-app/lib/ton-api-client.ts:
--------------------------------------------------------------------------------
1 | import { Client } from "@ston-fi/sdk";
2 |
3 | export const tonApiClient = new Client({
4 | endpoint: process.env.TON_API_URL ?? "https://toncenter.com/api/v2/jsonRPC",
5 | apiKey: process.env.TON_API_KEY,
6 | });
7 |
--------------------------------------------------------------------------------
/examples/next-js-app/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { TonAddressRegex } from "@/constants";
2 | import { type ClassValue, clsx } from "clsx";
3 | import { twMerge } from "tailwind-merge";
4 |
5 | export function cn(...inputs: ClassValue[]) {
6 | return twMerge(clsx(inputs));
7 | }
8 |
9 | /**
10 | * Converts a percentage to a percentage basis point (bps) value.
11 | */
12 | export function percentToPercentBps(percent: number): number {
13 | if (percent < 0 || percent > 1) {
14 | throw new Error("Invalid percent value. Must be between 0 and 1.");
15 | }
16 |
17 | return percent * 100 * 100;
18 | }
19 |
20 | /**
21 | * Divides a number by a given exponent of base 10 (10exponent), and formats it into a string representation of the number.
22 | *
23 | * @see [implementation by viem.](https://github.com/wevm/viem/blob/71a4e7aca259f0565005929d6584dca87bd59807/src/utils/unit/parseUnits.ts#L16)
24 | */
25 | export function floatToBigNumber(value: string, decimals: number) {
26 | let [integer = "0", fraction = "0"] = value.split(".");
27 |
28 | const negative = integer.startsWith("-");
29 |
30 | if (negative) integer = integer.slice(1);
31 |
32 | fraction = fraction.padEnd(decimals, "0").slice(0, decimals);
33 |
34 | return BigInt(`${negative ? "-" : ""}${integer}${fraction}`);
35 | }
36 |
37 | /**
38 | * Multiplies a string representation of a number by a given exponent of base 10 (10exponent).
39 | *
40 | * @see [implementation by viem.](https://github.com/wevm/viem/blob/71a4e7aca259f0565005929d6584dca87bd59807/src/utils/unit/formatUnits.ts#L16)
41 | */
42 | export function bigNumberToFloat(value: bigint | string, decimals: number) {
43 | let display = value.toString();
44 |
45 | const negative = display.startsWith("-");
46 | if (negative) display = display.slice(1);
47 |
48 | display = display.padStart(decimals, "0");
49 |
50 | const integer = display.slice(0, display.length - decimals);
51 | const fraction = display
52 | .slice(display.length - decimals)
53 | .replace(/(0+)$/, "");
54 |
55 | return `${negative ? "-" : ""}${integer || "0"}${
56 | fraction ? `.${fraction}` : ""
57 | }`;
58 | }
59 |
60 | export function validateFloatValue(value: string, decimals?: number) {
61 | const decimalsLimit = decimals ? `{0,${decimals}}` : "*";
62 | const regex = new RegExp(`^([0-9]+([.][0-9]${decimalsLimit})?|[.][0-9]+)$`);
63 | return regex.test(value);
64 | }
65 |
66 | /** convert from percent value in range 0.0 - 1.0 to BPS */
67 | export function percentToBps(percent: number) {
68 | return Math.floor(percent * 10000);
69 | }
70 |
71 | export const isValidAddress = (address: string) =>
72 | TonAddressRegex.test(address);
73 |
--------------------------------------------------------------------------------
/examples/next-js-app/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | output: "standalone",
4 | poweredByHeader: false,
5 |
6 | // enabling server side source maps
7 | /**
8 | *
9 | * @see https://nextjs.org/docs/messages/improper-devtool
10 | * NODE_OPTIONS='--inspect' next dev
11 | */
12 | webpack: (config, { isServer, dev }) => {
13 | if (isServer && !dev) {
14 | config.devtool = "source-map";
15 | }
16 | return config;
17 | },
18 | };
19 |
20 | export default nextConfig;
21 |
--------------------------------------------------------------------------------
/examples/next-js-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ston-fi/sdk-example-next-js-app",
3 | "private": true,
4 | "type": "module",
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "test": "pnpm run /^test:/",
11 | "test:typecheck": "tsc --noEmit"
12 | },
13 | "dependencies": {
14 | "@radix-ui/react-avatar": "1.1.9",
15 | "@radix-ui/react-dialog": "1.1.13",
16 | "@radix-ui/react-label": "2.1.6",
17 | "@radix-ui/react-popover": "1.1.13",
18 | "@radix-ui/react-slot": "1.2.2",
19 | "@radix-ui/react-toast": "1.2.13",
20 | "@ston-fi/api": "0.23.0",
21 | "@ston-fi/sdk": "workspace:*",
22 | "@tanstack/react-query": "5.76.1",
23 | "@ton/core": "0.60.1",
24 | "@ton/crypto": "3.3.0",
25 | "@ton/ton": "15.2.1",
26 | "@tonconnect/ui-react": "2.1.0",
27 | "class-variance-authority": "0.7.1",
28 | "clsx": "2.1.1",
29 | "cmdk": "1.1.1",
30 | "lucide-react": "0.511.0",
31 | "next": "15.3.2",
32 | "react": "19.1.0",
33 | "react-dom": "19.1.0",
34 | "tailwind-merge": "2.6.0",
35 | "tailwindcss-animate": "1.0.7"
36 | },
37 | "devDependencies": {
38 | "@ston-fi/typescript-config": "workspace:*",
39 | "@tailwindcss/postcss": "4.1.7",
40 | "@tanstack/react-query-devtools": "5.76.1",
41 | "@total-typescript/ts-reset": "^0.6.1",
42 | "@types/node": "^22.15.19",
43 | "@types/react": "19.1.4",
44 | "@types/react-dom": "19.1.5",
45 | "eslint": "^9.27.0",
46 | "eslint-config-next": "15.3.2",
47 | "globals": "15.15.0",
48 | "postcss": "8.5.3",
49 | "tailwindcss": "4.1.7"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/examples/next-js-app/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | "@tailwindcss/postcss": {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/examples/next-js-app/public/icons/gitbook.svg:
--------------------------------------------------------------------------------
1 | GitBook
--------------------------------------------------------------------------------
/examples/next-js-app/public/icons/github.svg:
--------------------------------------------------------------------------------
1 | GitHub
--------------------------------------------------------------------------------
/examples/next-js-app/public/tonconnect-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "url": "https://sdk-demo-app.ston.fi",
3 | "name": "SDK (demo)",
4 | "iconUrl": "https://static.ston.fi/logo/external-logo.jpg"
5 | }
6 |
--------------------------------------------------------------------------------
/examples/next-js-app/ts-reset.d.ts:
--------------------------------------------------------------------------------
1 | import "@total-typescript/ts-reset";
2 |
--------------------------------------------------------------------------------
/examples/next-js-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@ston-fi/typescript-config/tsconfig.json",
3 | "compilerOptions": {
4 | "paths": {
5 | "@/*": ["./*"]
6 | },
7 | "lib": ["es2022", "DOM", "DOM.Iterable"],
8 | "plugins": [
9 | {
10 | "name": "next"
11 | }
12 | ],
13 | "target": "ESNext",
14 | "module": "ESNext",
15 | "moduleResolution": "Bundler",
16 | "jsx": "preserve",
17 | "incremental": true
18 | },
19 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
20 | "exclude": ["node_modules"]
21 | }
22 |
--------------------------------------------------------------------------------
/lefthook.yml:
--------------------------------------------------------------------------------
1 | # Refer for explanation to following link:
2 | # https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
3 |
4 | pre-commit:
5 | commands:
6 | check:
7 | glob: "*"
8 | run: npx @biomejs/biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
9 | stage_fixed: true
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "packageManager": "pnpm@10.0.0",
5 | "scripts": {
6 | "preinstall": "npx only-allow pnpm",
7 | "cleanup": "rm -rf .turbo node_modules ./examples/**/{.next,.turbo,node_modules} ./packages/**/{.turbo,coverage,dist,node_modules,build-report.html}",
8 | "lint": "biome lint ./*",
9 | "format": "biome format ./*",
10 | "test": "turbo run test"
11 | },
12 | "devDependencies": {
13 | "@biomejs/biome": "1.9.4",
14 | "lefthook": "1.11.12",
15 | "turbo": "2.5.3",
16 | "typescript": "^5"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/sdk/README.md:
--------------------------------------------------------------------------------
1 |
2 |
STON.fi SDK
3 |
4 |
5 | [](https://ton.org/)
6 | [](https://img.shields.io/npm/l/@ston-fi/sdk)
7 | [](https://www.npmjs.com/package/@ston-fi/sdk/v/latest)
8 |
9 | The SDK is written in TypeScript and designed to be a thin wrapper on top of the [STON.fi](https://ston.fi/) contracts, which will help STON.fi protocol to be used more easily in JS/TS projects
10 |
11 | Documentation for the SDK is available at [docs.ston.fi](https://docs.ston.fi/docs/developer-section/sdk)
12 |
13 | ## Installation
14 |
15 | Firstly install the [@ton/ton](https://github.com/ton-org/ton) package following their [installation guide](https://github.com/ton-org/ton?tab=readme-ov-file#install)
16 |
17 | Then, add SDK package using the package manager of your choice.
18 |
19 | ### NPM
20 |
21 | ```bash
22 | npm install @ston-fi/sdk
23 | ```
24 |
25 | ### Yarn
26 |
27 | ```bash
28 | yarn add @ston-fi/sdk
29 | ```
30 |
31 | ### PNPM
32 |
33 | ```bash
34 | pnpm install @ston-fi/sdk
35 | ```
36 |
37 | ## Next steps
38 |
39 | ### Take a look at the demo app
40 |
41 | We are providing a simple but fully functional demo app with the SDK usage in the next.js app to demonstrate the SDK functionality. The source code is open-sourced and can be found [here](https://github.com/ston-fi/sdk/tree/main/examples/next-js-app). Try this app at https://sdk-demo-app.ston.fi
42 |
43 | ### Dive deep into the documentation
44 |
45 | - [DEX guide](https://docs.ston.fi/docs/developer-section/sdk)
46 | - [Swap](https://docs.ston.fi/docs/developer-section/sdk/dex-v2/swap)
47 | - [Provide liquidity](https://docs.ston.fi/docs/developer-section/sdk/dex-v2/lp_provide)
48 | - [Transaction setting guide](https://docs.ston.fi/docs/developer-section/sdk/transaction-sending)
49 |
--------------------------------------------------------------------------------
/packages/sdk/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ston-fi/sdk",
3 | "version": "2.4.1",
4 | "description": "Typescript SDK to interact with the Ston.fi DEX",
5 | "license": "MIT",
6 | "homepage": "https://github.com/ston-fi/sdk#readme",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/ston-fi/sdk.git",
10 | "directory": "packages/sdk"
11 | },
12 | "type": "module",
13 | "packageManager": "pnpm@9.0.0",
14 | "files": ["dist", "package.json", "README.md"],
15 | "main": "./dist/index.cjs",
16 | "module": "./dist/index.js",
17 | "types": "./dist/index.d.ts",
18 | "sideEffects": false,
19 | "exports": {
20 | "./package.json": "./package.json",
21 | ".": {
22 | "import": "./dist/index.js",
23 | "default": "./dist/index.cjs"
24 | },
25 | "./dex/v1": {
26 | "import": "./dist/contracts/dex/v1/index.js",
27 | "default": "./dist/contracts/dex/v1/index.cjs"
28 | },
29 | "./dex/v2_1": {
30 | "import": "./dist/contracts/dex/v2_1/index.js",
31 | "default": "./dist/contracts/dex/v2_1/index.cjs"
32 | },
33 | "./dex/v2_2": {
34 | "import": "./dist/contracts/dex/v2_2/index.js",
35 | "default": "./dist/contracts/dex/v2_2/index.cjs"
36 | },
37 | "./farm/v1": {
38 | "import": "./dist/contracts/farm/v1/index.js",
39 | "default": "./dist/contracts/farm/v1/index.cjs"
40 | },
41 | "./farm/v2": {
42 | "import": "./dist/contracts/farm/v2/index.js",
43 | "default": "./dist/contracts/farm/v2/index.cjs"
44 | },
45 | "./farm/v3": {
46 | "import": "./dist/contracts/farm/v3/index.js",
47 | "default": "./dist/contracts/farm/v3/index.cjs"
48 | }
49 | },
50 | "scripts": {
51 | "format": "pnpm run /^format:.*/",
52 | "format:biome": "biome format --write",
53 | "lint": "pnpm run /^lint:.*/",
54 | "lint:tsc": "tsc --noEmit --pretty",
55 | "lint:biome": "biome check",
56 | "inspect:unused-code": "pnpm dlx knip",
57 | "inspect:circular-dependencies": "npx --yes madge --circular --extensions js,ts .",
58 | "test": "vitest",
59 | "build": "tsup",
60 | "prepublishOnly": "pnpm run format && pnpm run lint && pnpm run test --run && pnpm run build && pnpm pack && pnpm attw --pack *.tgz --profile node16 && rm *.tgz && pnpm publint"
61 | },
62 | "peerDependencies": {
63 | "@ston-fi/api": "^0",
64 | "@ton/ton": "^13.9.0 || ^14.0.0 || ^15.0.0"
65 | },
66 | "devDependencies": {
67 | "@arethetypeswrong/cli": "^0.17.4",
68 | "@ston-fi/typescript-config": "workspace:*",
69 | "esbuild-analyzer": "^0.2.0",
70 | "publint": "^0.3.11",
71 | "tsup": "8.3.5",
72 | "vitest": "3.1.1"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/sdk/src/client/Client.ts:
--------------------------------------------------------------------------------
1 | import { StonApiClient } from "@ston-fi/api";
2 | import { Address, TonClient, TupleReader, beginCell } from "@ton/ton";
3 |
4 | export class Client extends TonClient {
5 | private stonApiClient: StonApiClient;
6 |
7 | constructor(
8 | options: ConstructorParameters[0] & {
9 | stonApiClient?: StonApiClient;
10 | },
11 | ) {
12 | super(options);
13 |
14 | this.stonApiClient = options.stonApiClient ?? new StonApiClient();
15 | }
16 |
17 | public override async callGetMethod(
18 | ...args: Parameters
19 | ) {
20 | if (args[1] === "get_wallet_address" && args[2]?.[0]?.type === "slice") {
21 | try {
22 | const jettonWalletAddress =
23 | await this.stonApiClient.getJettonWalletAddress({
24 | jettonAddress: args[0].toString(),
25 | ownerAddress: args[2][0].cell.beginParse().loadAddress().toString(),
26 | });
27 |
28 | return {
29 | gas_used: 0,
30 | stack: new TupleReader([
31 | {
32 | type: "slice",
33 | cell: beginCell()
34 | .storeAddress(Address.parse(jettonWalletAddress))
35 | .endCell(),
36 | },
37 | ]),
38 | };
39 | } catch {
40 | //
41 | }
42 | }
43 |
44 | return super.callGetMethod(...args);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/client/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./Client";
2 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/core/Contract.ts:
--------------------------------------------------------------------------------
1 | import type { Address, Contract as ContractInterface } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../types";
4 | import { toAddress } from "../../utils/toAddress";
5 |
6 | // biome-ignore lint/suspicious/noEmptyInterface: it is empty for base class but may be extended in derived classes
7 | export interface ContractOptions {}
8 |
9 | export abstract class Contract implements ContractInterface {
10 | public readonly address: Address;
11 |
12 | constructor(address: AddressType, options?: ContractOptions) {
13 | this.address = toAddress(address);
14 | }
15 |
16 | public static create<
17 | T extends Contract,
18 | C extends new (
19 | address: AddressType,
20 | ) => T,
21 | >(this: C, address: AddressType) {
22 | // biome-ignore lint/complexity/noThisInStatic: this here is a derived class
23 | return new this(address) as InstanceType;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/core/JettonMinter.ts:
--------------------------------------------------------------------------------
1 | import { type ContractProvider, beginCell } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../types";
4 | import { toAddress } from "../../utils/toAddress";
5 | import { Contract } from "./Contract";
6 |
7 | export class JettonMinter extends Contract {
8 | async getWalletAddress(
9 | provider: ContractProvider,
10 | ownerAddress: AddressType,
11 | ) {
12 | const result = await provider.get("get_wallet_address", [
13 | {
14 | type: "slice",
15 | cell: beginCell().storeAddress(toAddress(ownerAddress)).endCell(),
16 | },
17 | ]);
18 |
19 | return result.stack.readAddress();
20 | }
21 |
22 | async getJettonData(provider: ContractProvider) {
23 | const result = await provider.get("get_jetton_data", []);
24 |
25 | const jettonData = {
26 | totalSupply: result.stack.readBigNumber(),
27 | canIncSupply: Boolean(result.stack.readNumber()),
28 | adminAddress: result.stack.readAddressOpt(),
29 | contentRaw: result.stack.readCell(),
30 | jettonWalletCode: result.stack.readCell(),
31 | };
32 |
33 | return jettonData;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/core/JettonWallet.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import { Contract } from "./Contract";
4 |
5 | export class JettonWallet extends Contract {
6 | async getBalance(provider: ContractProvider) {
7 | const state = await provider.getState();
8 |
9 | if (state.state.type !== "active") {
10 | return BigInt(0);
11 | }
12 |
13 | const { balance } = await this.getWalletData(provider);
14 |
15 | return balance;
16 | }
17 |
18 | async getWalletData(provider: ContractProvider) {
19 | const result = await provider.get("get_wallet_data", []);
20 |
21 | return {
22 | balance: result.stack.readBigNumber(),
23 | ownerAddress: result.stack.readAddress(),
24 | jettonMasterAddress: result.stack.readAddress(),
25 | jettonWalletCode: result.stack.readCell(),
26 | };
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/core/constants.ts:
--------------------------------------------------------------------------------
1 | import { Address } from "@ton/ton";
2 |
3 | export const HOLE_ADDRESS = Address.parse(
4 | "0:0000000000000000000000000000000000000000000000000000000000000000",
5 | );
6 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEX_VERSION = {
2 | v1: "v1",
3 | /**
4 | * We recommend using `v2_1` contracts
5 | * only for withdrawal functionality on already deployed contracts.
6 | * @see https://t.me/stonfidex/712
7 | */
8 | v2_1: "v2_1",
9 | v2_2: "v2_2",
10 | } as const;
11 |
12 | export type DEX_VERSION = (typeof DEX_VERSION)[keyof typeof DEX_VERSION];
13 |
14 | export const DEX_TYPE = {
15 | CPI: "constant_product",
16 | Stable: "stableswap",
17 | WCPI: "weighted_const_product",
18 | WStable: "weighted_stableswap",
19 | } as const;
20 |
21 | export type DEX_TYPE = (typeof DEX_TYPE)[keyof typeof DEX_TYPE];
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/errors.ts:
--------------------------------------------------------------------------------
1 | import type { pTON_VERSION } from "../pTON/constants";
2 |
3 | export class UnmatchedPtonVersion extends Error {
4 | constructor({
5 | expected,
6 | received,
7 | }: {
8 | expected: pTON_VERSION;
9 | received: pTON_VERSION;
10 | }) {
11 | super(
12 | `The version of the provided pTON (${received}) does not match the expected version (${expected})`,
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/index.ts:
--------------------------------------------------------------------------------
1 | import { DEX_VERSION } from "./constants";
2 | import { DEX as DEXv1 } from "./v1";
3 | import { DEX as DEXv2_1 } from "./v2_1";
4 | import { DEX as DEXv2_2 } from "./v2_2";
5 |
6 | export { DEX_VERSION, DEX_TYPE } from "./constants";
7 |
8 | export const DEX = {
9 | [DEX_VERSION.v1]: DEXv1,
10 | [DEX_VERSION.v2_1]: DEXv2_1,
11 | [DEX_VERSION.v2_2]: DEXv2_2,
12 | } as const;
13 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v1/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEX_OP_CODES = {
2 | SWAP: 0x25938561,
3 | PROVIDE_LP: 0xfcf9e58f,
4 | DIRECT_ADD_LIQUIDITY: 0x4cf82803,
5 | REFUND_ME: 0x0bf3f447,
6 | RESET_GAS: 0x42a0fb43,
7 | COLLECT_FEES: 0x1fcb7d3d,
8 | BURN: 0x595f07bc,
9 | } as const;
10 |
11 | export const ROUTER_ADDRESS =
12 | "EQB3ncyBUTjZUA5EnFKR5_EnOMI9V1tTEAAPaiU71gc4TiUt";
13 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v1/index.ts:
--------------------------------------------------------------------------------
1 | import { LpAccountV1 } from "./LpAccountV1";
2 | import { PoolV1 } from "./PoolV1";
3 | import { RouterV1 } from "./RouterV1";
4 |
5 | import { PtonV1 } from "../../pTON/v1/PtonV1";
6 |
7 | export const DEX = {
8 | Router: RouterV1,
9 | Pool: PoolV1,
10 | LpAccount: LpAccountV1,
11 | pTON: PtonV1,
12 | } as const;
13 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/constants.ts:
--------------------------------------------------------------------------------
1 | export const DEX_OP_CODES = {
2 | SWAP: 0x6664de2a,
3 | CROSS_SWAP: 0x69cf1a5b,
4 | PROVIDE_LP: 0x37c096df,
5 | DIRECT_ADD_LIQUIDITY: 0xff8bfc6,
6 | REFUND_ME: 0x132b9a2c,
7 | RESET_GAS: 0x29d22935,
8 | COLLECT_FEES: 0x1ee4911e,
9 | BURN: 0x595f07bc,
10 | WITHDRAW_FEE: 0x354bcdf4,
11 | } as const;
12 |
13 | export const TX_DEADLINE = 15 * 60; // 15 minutes
14 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/index.ts:
--------------------------------------------------------------------------------
1 | import { PtonV2_1 } from "../../pTON/v2_1/PtonV2_1";
2 |
3 | import { BaseRouterV2_1 } from "./router/BaseRouterV2_1";
4 | import { CPIRouterV2_1 } from "./router/CPIRouterV2_1";
5 | import { StableRouterV2_1 } from "./router/StableRouterV2_1";
6 | import { WCPIRouterV2_1 } from "./router/WCPIRouterV2_1";
7 | import { WStableRouterV2_1 } from "./router/WStableRouterV2_1";
8 |
9 | import { BasePoolV2_1 } from "./pool/BasePoolV2_1";
10 | import { CPIPoolV2_1 } from "./pool/CPIPoolV2_1";
11 | import { StablePoolV2_1 } from "./pool/StablePoolV2_1";
12 | import { WCPIPoolV2_1 } from "./pool/WCPIPoolV2_1";
13 | import { WStablePoolV2_1 } from "./pool/WStablePoolV2_1";
14 |
15 | import { LpAccountV2_1 } from "./LpAccount/LpAccountV2_1";
16 |
17 | import { VaultV2_1 } from "./vault/VaultV2_1";
18 |
19 | export { CPIRouterV2_1, StableRouterV2_1, WCPIRouterV2_1, WStableRouterV2_1 };
20 | export { CPIPoolV2_1, StablePoolV2_1, WCPIPoolV2_1, WStablePoolV2_1 };
21 | export { LpAccountV2_1 };
22 | export { VaultV2_1 } from "./vault/VaultV2_1";
23 |
24 | /** @deprecated. Use explicit Router instead (e.g. DEX.Router.CPI) or use `dexFactory` */
25 | export class RouterV2_1 extends BaseRouterV2_1 {
26 | public static readonly CPI = CPIRouterV2_1;
27 | public static readonly Stable = StableRouterV2_1;
28 | public static readonly WCPI = WCPIRouterV2_1;
29 | public static readonly WStable = WStableRouterV2_1;
30 | }
31 |
32 | /** @deprecated. Use explicit Pool instead (e.g. DEX.Pool.CPI) or use `dexFactory` */
33 | export class PoolV2_1 extends BasePoolV2_1 {
34 | public static readonly CPI = CPIPoolV2_1;
35 | public static readonly Stable = StablePoolV2_1;
36 | public static readonly WCPI = WCPIPoolV2_1;
37 | public static readonly WStable = WStablePoolV2_1;
38 | }
39 |
40 | export const DEX = {
41 | Router: RouterV2_1,
42 | Pool: PoolV2_1,
43 | LpAccount: LpAccountV2_1,
44 | Vault: VaultV2_1,
45 | pTON: PtonV2_1,
46 | } as const;
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/CPIPoolV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { LpAccountV2_1 } from "../LpAccount/LpAccountV2_1";
10 | import { CPIPoolV2_1 } from "./CPIPoolV2_1";
11 |
12 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
13 | const POOL_ADDRESS = "kQDZVuwLe9I6XjZrnm3txQyEHuV5RcwlSRVHpqiutLsw6HR7"; // TestRED/TestBLUE pool
14 |
15 | describe("CPIPoolV2_1", () => {
16 | beforeAll(setup);
17 |
18 | describe("version", () => {
19 | it("should have expected static value", () => {
20 | expect(CPIPoolV2_1.version).toBe(DEX_VERSION.v2_1);
21 | });
22 | });
23 |
24 | describe("dexType", () => {
25 | it("should have expected static value", () => {
26 | expect(CPIPoolV2_1.dexType).toBe(DEX_TYPE.CPI);
27 | });
28 | });
29 |
30 | describe("getLpAccount", () => {
31 | it("should return LpAccount instance with expected version", async () => {
32 | const snapshot = createProviderSnapshot().cell(
33 | "te6cckEBAQEAJAAAQ4AVCq1wcAqyiCdZCD0uy2zlKSKJURP53P8BGQXdhxOazjC3iKkE",
34 | );
35 | const provider = createMockProviderFromSnapshot(snapshot);
36 |
37 | const contract = provider.open(CPIPoolV2_1.create(POOL_ADDRESS));
38 |
39 | const lpAccount = await contract.getLpAccount({
40 | ownerAddress: USER_WALLET_ADDRESS,
41 | });
42 |
43 | expect(lpAccount).toBeInstanceOf(LpAccountV2_1);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/CPIPoolV2_1.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import { DEX_TYPE } from "../../constants";
4 | import { BasePoolV2_1 } from "./BasePoolV2_1";
5 |
6 | export class CPIPoolV2_1 extends BasePoolV2_1 {
7 | public static readonly dexType = DEX_TYPE.CPI;
8 |
9 | public override async getPoolData(provider: ContractProvider) {
10 | const data = await this.implGetPoolData(provider);
11 |
12 | return {
13 | ...data.commonPoolData,
14 | };
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/StablePoolV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { LpAccountV2_1 } from "../LpAccount/LpAccountV2_1";
10 | import { StablePoolV2_1 } from "./StablePoolV2_1";
11 |
12 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
13 | const POOL_ADDRESS = "kQDZVuwLe9I6XjZrnm3txQyEHuV5RcwlSRVHpqiutLsw6HR7"; // TestRED/TestBLUE pool
14 |
15 | describe("StablePoolV2_1", () => {
16 | beforeAll(setup);
17 |
18 | describe("version", () => {
19 | it("should have expected static value", () => {
20 | expect(StablePoolV2_1.version).toBe(DEX_VERSION.v2_1);
21 | });
22 | });
23 |
24 | describe("dexType", () => {
25 | it("should have expected static value", () => {
26 | expect(StablePoolV2_1.dexType).toBe(DEX_TYPE.Stable);
27 | });
28 | });
29 |
30 | describe("getLpAccount", () => {
31 | it("should return LpAccount instance with expected version", async () => {
32 | const snapshot = createProviderSnapshot().cell(
33 | "te6cckEBAQEAJAAAQ4AVCq1wcAqyiCdZCD0uy2zlKSKJURP53P8BGQXdhxOazjC3iKkE",
34 | );
35 | const provider = createMockProviderFromSnapshot(snapshot);
36 |
37 | const contract = provider.open(StablePoolV2_1.create(POOL_ADDRESS));
38 |
39 | const lpAccount = await contract.getLpAccount({
40 | ownerAddress: USER_WALLET_ADDRESS,
41 | });
42 |
43 | expect(lpAccount).toBeInstanceOf(LpAccountV2_1);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/StablePoolV2_1.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import { DEX_TYPE } from "../../constants";
4 | import { BasePoolV2_1 } from "./BasePoolV2_1";
5 |
6 | export class StablePoolV2_1 extends BasePoolV2_1 {
7 | public static readonly dexType = DEX_TYPE.Stable;
8 |
9 | public override async getPoolData(provider: ContractProvider) {
10 | const data = await this.implGetPoolData(provider);
11 |
12 | return {
13 | ...data.commonPoolData,
14 | amp: data.stack.readBigNumber(),
15 | };
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/WCPIPoolV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WCPIPoolV2_1 } from "./WCPIPoolV2_1";
6 |
7 | describe("WCPIPoolV2_1", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WCPIPoolV2_1.version).toBe(DEX_VERSION.v2_1);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WCPIPoolV2_1.dexType).toBe(DEX_TYPE.WCPI);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/WCPIPoolV2_1.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import { DEX_TYPE } from "../../constants";
4 | import { BasePoolV2_1 } from "./BasePoolV2_1";
5 |
6 | export class WCPIPoolV2_1 extends BasePoolV2_1 {
7 | public static readonly dexType = DEX_TYPE.WCPI;
8 |
9 | public override async getPoolData(provider: ContractProvider) {
10 | const data = await this.implGetPoolData(provider);
11 |
12 | return {
13 | ...data.commonPoolData,
14 | };
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/WStablePoolV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WStablePoolV2_1 } from "./WStablePoolV2_1";
6 |
7 | describe("WStablePoolV2_1", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WStablePoolV2_1.version).toBe(DEX_VERSION.v2_1);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WStablePoolV2_1.dexType).toBe(DEX_TYPE.WStable);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/pool/WStablePoolV2_1.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import { DEX_TYPE } from "../../constants";
4 | import { BasePoolV2_1 } from "./BasePoolV2_1";
5 |
6 | export class WStablePoolV2_1 extends BasePoolV2_1 {
7 | public static readonly dexType = DEX_TYPE.WStable;
8 |
9 | public override async getPoolData(provider: ContractProvider) {
10 | const data = await this.implGetPoolData(provider);
11 |
12 | return {
13 | ...data.commonPoolData,
14 | amp: data.stack.readBigNumber(),
15 | rate: data.stack.readBigNumber(),
16 | w0: data.stack.readBigNumber(),
17 | rateSetterAddress: data.stack.readAddressOpt(),
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/CPIRouterV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { CPIPoolV2_1 } from "../pool/CPIPoolV2_1";
10 | import { VaultV2_1 } from "../vault/VaultV2_1";
11 | import { CPIRouterV2_1 } from "./CPIRouterV2_1";
12 |
13 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
14 | const ROUTER_ADDRESS = "kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v";
15 | const OFFER_JETTON_ADDRESS = "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5"; // TestRED
16 | const ASK_JETTON_ADDRESS = "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3"; // TestBLUE
17 |
18 | describe("CPIRouterV2_1", () => {
19 | beforeAll(setup);
20 |
21 | describe("version", () => {
22 | it("should have expected static value", () => {
23 | expect(CPIRouterV2_1.version).toBe(DEX_VERSION.v2_1);
24 | });
25 | });
26 |
27 | describe("dexType", () => {
28 | it("should have expected static value", () => {
29 | expect(CPIRouterV2_1.dexType).toBe(DEX_TYPE.CPI);
30 | });
31 | });
32 |
33 | describe("getPool", () => {
34 | it("should return Pool instance with expected version", async () => {
35 | const snapshot = createProviderSnapshot().cell(
36 | "te6cckEBAQEAJAAAQ4AbKt2Bb3pHS8bNc829uKGQg9yvKLmEqSKo9NUV1pdmHRDqCLOi",
37 | );
38 | const provider = createMockProviderFromSnapshot(snapshot);
39 |
40 | const contract = provider.open(CPIRouterV2_1.create(ROUTER_ADDRESS));
41 |
42 | const pool = await contract.getPool({
43 | token0: OFFER_JETTON_ADDRESS,
44 | token1: ASK_JETTON_ADDRESS,
45 | });
46 |
47 | expect(pool).toBeInstanceOf(CPIPoolV2_1);
48 | });
49 | });
50 |
51 | describe("getVault", () => {
52 | it("should return Vault instance with expected version", async () => {
53 | const snapshot = createProviderSnapshot().cell(
54 | "te6cckEBAQEAJAAAQ4APnbYFk/sHIc85nyovU2iRke85JwO8rxYn6ilYMKhtfzAcWMdK",
55 | );
56 | const provider = createMockProviderFromSnapshot(snapshot);
57 |
58 | const contract = provider.open(CPIRouterV2_1.create(ROUTER_ADDRESS));
59 |
60 | const vault = await contract.getVault({
61 | user: USER_WALLET_ADDRESS,
62 | tokenMinter: OFFER_JETTON_ADDRESS,
63 | });
64 |
65 | expect(vault).toBeInstanceOf(VaultV2_1);
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/CPIRouterV2_1.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_TYPE } from "../../constants";
5 | import { CPIPoolV2_1 } from "../pool/CPIPoolV2_1";
6 | import { BaseRouterV2_1 } from "./BaseRouterV2_1";
7 |
8 | export class CPIRouterV2_1 extends BaseRouterV2_1 {
9 | public static readonly dexType = DEX_TYPE.CPI;
10 |
11 | public override async getPool(
12 | provider: ContractProvider,
13 | params: {
14 | token0: AddressType;
15 | token1: AddressType;
16 | },
17 | ) {
18 | const poolAddress = await this.getPoolAddressByJettonMinters(
19 | provider,
20 | params,
21 | );
22 |
23 | return CPIPoolV2_1.create(poolAddress);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/StableRouterV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { StablePoolV2_1 } from "../pool/StablePoolV2_1";
10 | import { VaultV2_1 } from "../vault/VaultV2_1";
11 | import { StableRouterV2_1 } from "./StableRouterV2_1";
12 |
13 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
14 | const ROUTER_ADDRESS = "kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v";
15 | const OFFER_JETTON_ADDRESS = "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5"; // TestRED
16 | const ASK_JETTON_ADDRESS = "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3"; // TestBLUE
17 |
18 | describe("StableRouterV2_1", () => {
19 | beforeAll(setup);
20 |
21 | describe("version", () => {
22 | it("should have expected static value", () => {
23 | expect(StableRouterV2_1.version).toBe(DEX_VERSION.v2_1);
24 | });
25 | });
26 |
27 | describe("dexType", () => {
28 | it("should have expected static value", () => {
29 | expect(StableRouterV2_1.dexType).toBe(DEX_TYPE.Stable);
30 | });
31 | });
32 |
33 | describe("getPool", () => {
34 | it("should return Pool instance with expected version", async () => {
35 | const snapshot = createProviderSnapshot().cell(
36 | "te6cckEBAQEAJAAAQ4AbKt2Bb3pHS8bNc829uKGQg9yvKLmEqSKo9NUV1pdmHRDqCLOi",
37 | );
38 | const provider = createMockProviderFromSnapshot(snapshot);
39 |
40 | const contract = provider.open(StableRouterV2_1.create(ROUTER_ADDRESS));
41 |
42 | const pool = await contract.getPool({
43 | token0: OFFER_JETTON_ADDRESS,
44 | token1: ASK_JETTON_ADDRESS,
45 | });
46 |
47 | expect(pool).toBeInstanceOf(StablePoolV2_1);
48 | });
49 | });
50 |
51 | describe("getVault", () => {
52 | it("should return Vault instance with expected version", async () => {
53 | const snapshot = createProviderSnapshot().cell(
54 | "te6cckEBAQEAJAAAQ4APnbYFk/sHIc85nyovU2iRke85JwO8rxYn6ilYMKhtfzAcWMdK",
55 | );
56 | const provider = createMockProviderFromSnapshot(snapshot);
57 |
58 | const contract = provider.open(StableRouterV2_1.create(ROUTER_ADDRESS));
59 |
60 | const vault = await contract.getVault({
61 | user: USER_WALLET_ADDRESS,
62 | tokenMinter: OFFER_JETTON_ADDRESS,
63 | });
64 |
65 | expect(vault).toBeInstanceOf(VaultV2_1);
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/StableRouterV2_1.ts:
--------------------------------------------------------------------------------
1 | import { type ContractProvider, toNano } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_TYPE } from "../../constants";
5 | import { StablePoolV2_1 } from "../pool/StablePoolV2_1";
6 | import { BaseRouterV2_1, type BaseRouterV2_1Options } from "./BaseRouterV2_1";
7 |
8 | export class StableRouterV2_1 extends BaseRouterV2_1 {
9 | public static readonly dexType = DEX_TYPE.Stable;
10 |
11 | public static override readonly gasConstants = {
12 | ...BaseRouterV2_1.gasConstants,
13 | swapJettonToJetton: {
14 | gasAmount: toNano("0.329"),
15 | forwardGasAmount: toNano("0.269"),
16 | },
17 | swapJettonToTon: {
18 | gasAmount: toNano("0.329"),
19 | forwardGasAmount: toNano("0.269"),
20 | },
21 | swapTonToJetton: {
22 | forwardGasAmount: toNano("0.329"),
23 | },
24 | };
25 |
26 | constructor(
27 | address: AddressType,
28 | { gasConstants, ...options }: BaseRouterV2_1Options = {},
29 | ) {
30 | super(address, {
31 | ...options,
32 | gasConstants: {
33 | ...StableRouterV2_1.gasConstants,
34 | ...gasConstants,
35 | },
36 | });
37 | }
38 |
39 | public override async getPool(
40 | provider: ContractProvider,
41 | params: {
42 | token0: AddressType;
43 | token1: AddressType;
44 | },
45 | ) {
46 | const poolAddress = await this.getPoolAddressByJettonMinters(
47 | provider,
48 | params,
49 | );
50 |
51 | return StablePoolV2_1.create(poolAddress);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/WCPIRouterV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WCPIRouterV2_1 } from "./WCPIRouterV2_1";
6 |
7 | describe("WCPIRouterV2_1", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WCPIRouterV2_1.version).toBe(DEX_VERSION.v2_1);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WCPIRouterV2_1.dexType).toBe(DEX_TYPE.WCPI);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/WCPIRouterV2_1.ts:
--------------------------------------------------------------------------------
1 | import { type ContractProvider, toNano } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_TYPE } from "../../constants";
5 | import { WCPIPoolV2_1 } from "../pool/WCPIPoolV2_1";
6 | import { BaseRouterV2_1, type BaseRouterV2_1Options } from "./BaseRouterV2_1";
7 |
8 | export class WCPIRouterV2_1 extends BaseRouterV2_1 {
9 | public static readonly dexType = DEX_TYPE.WCPI;
10 |
11 | public static override readonly gasConstants = {
12 | ...BaseRouterV2_1.gasConstants,
13 | swapJettonToJetton: {
14 | gasAmount: toNano("0.319"),
15 | forwardGasAmount: toNano("0.259"),
16 | },
17 | swapJettonToTon: {
18 | gasAmount: toNano("0.319"),
19 | forwardGasAmount: toNano("0.259"),
20 | },
21 | swapTonToJetton: {
22 | forwardGasAmount: toNano("0.319"),
23 | },
24 | };
25 |
26 | constructor(
27 | address: AddressType,
28 | { gasConstants, ...options }: BaseRouterV2_1Options = {},
29 | ) {
30 | super(address, {
31 | ...options,
32 | gasConstants: {
33 | ...WCPIRouterV2_1.gasConstants,
34 | ...gasConstants,
35 | },
36 | });
37 | }
38 |
39 | public override async getPool(
40 | provider: ContractProvider,
41 | params: {
42 | token0: AddressType;
43 | token1: AddressType;
44 | },
45 | ) {
46 | const poolAddress = await this.getPoolAddressByJettonMinters(
47 | provider,
48 | params,
49 | );
50 |
51 | return WCPIPoolV2_1.create(poolAddress);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/WStableRouterV2_1.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WStableRouterV2_1 } from "./WStableRouterV2_1";
6 |
7 | describe("WStableRouterV2_1", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WStableRouterV2_1.version).toBe(DEX_VERSION.v2_1);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WStableRouterV2_1.dexType).toBe(DEX_TYPE.WStable);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/router/WStableRouterV2_1.ts:
--------------------------------------------------------------------------------
1 | import { type ContractProvider, toNano } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_TYPE } from "../../constants";
5 | import { WStablePoolV2_1 } from "../pool/WStablePoolV2_1";
6 | import { BaseRouterV2_1, type BaseRouterV2_1Options } from "./BaseRouterV2_1";
7 |
8 | export class WStableRouterV2_1 extends BaseRouterV2_1 {
9 | public static readonly dexType = DEX_TYPE.WStable;
10 |
11 | public static override readonly gasConstants = {
12 | ...BaseRouterV2_1.gasConstants,
13 | swapJettonToJetton: {
14 | gasAmount: toNano("0.479"),
15 | forwardGasAmount: toNano("0.419"),
16 | },
17 | swapJettonToTon: {
18 | gasAmount: toNano("0.479"),
19 | forwardGasAmount: toNano("0.419"),
20 | },
21 | swapTonToJetton: {
22 | forwardGasAmount: toNano("0.479"),
23 | },
24 | };
25 |
26 | constructor(
27 | address: AddressType,
28 | { gasConstants, ...options }: BaseRouterV2_1Options = {},
29 | ) {
30 | super(address, {
31 | ...options,
32 | gasConstants: {
33 | ...WStableRouterV2_1.gasConstants,
34 | ...gasConstants,
35 | },
36 | });
37 | }
38 |
39 | public override async getPool(
40 | provider: ContractProvider,
41 | params: {
42 | token0: AddressType;
43 | token1: AddressType;
44 | },
45 | ) {
46 | const poolAddress = await this.getPoolAddressByJettonMinters(
47 | provider,
48 | params,
49 | );
50 |
51 | return WStablePoolV2_1.create(poolAddress);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_1/vault/VaultV2_1.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Cell,
3 | type ContractProvider,
4 | type Sender,
5 | type SenderArguments,
6 | beginCell,
7 | toNano,
8 | } from "@ton/ton";
9 |
10 | import type { AddressType, AmountType, QueryIdType } from "../../../../types";
11 | import { Contract, type ContractOptions } from "../../../core/Contract";
12 | import { DEX_VERSION } from "../../constants";
13 | import { DEX_OP_CODES } from "../constants";
14 |
15 | export interface VaultV2_1Options extends ContractOptions {
16 | gasConstants?: Partial;
17 | }
18 |
19 | /**
20 | * Token vault stores referral fees on a separate contract similar to an LP account.
21 | * This will allow us to decrease TX fees for swaps since users won't have to pay for additional Jetton transfer TX.
22 | *
23 | * Vault address is defined by router_address, owner_address and router_token_Wallet_address,
24 | * so, for each token, each user can have a dedicated vault contract.
25 | */
26 | export class VaultV2_1 extends Contract {
27 | public static readonly version: DEX_VERSION = DEX_VERSION.v2_1;
28 |
29 | public static readonly gasConstants = {
30 | withdrawFee: toNano("0.3"),
31 | };
32 |
33 | public readonly gasConstants;
34 |
35 | constructor(
36 | address: AddressType,
37 | { gasConstants, ...options }: VaultV2_1Options = {},
38 | ) {
39 | super(address, options);
40 |
41 | this.gasConstants = {
42 | ...VaultV2_1.gasConstants,
43 | ...gasConstants,
44 | };
45 | }
46 |
47 | public async createWithdrawFeeBody(params?: {
48 | queryId?: QueryIdType;
49 | }): Promise {
50 | return beginCell()
51 | .storeUint(DEX_OP_CODES.WITHDRAW_FEE, 32)
52 | .storeUint(params?.queryId ?? 0, 64)
53 | .endCell();
54 | }
55 |
56 | /**
57 | * Build all data required to execute a `withdraw_fee` transaction.
58 | *
59 | * @param {ContractProvider} provider - {@link ContractProvider} instance
60 | *
61 | * @param {object | undefined} params - Optional tx params
62 | * @param {bigint | number | string | undefined} params.gasAmount - Optional; Custom transaction gas amount (in nanoTons)
63 | * @param {bigint | number | undefined} params.queryId - Optional; query id
64 | *
65 | *
66 | * @returns {SenderArguments} all data required to execute a `withdraw_fee` transaction.
67 | */
68 | public async getWithdrawFeeTxParams(
69 | provider: ContractProvider,
70 | params?: {
71 | gasAmount?: AmountType;
72 | queryId?: QueryIdType;
73 | },
74 | ): Promise {
75 | const to = this.address;
76 |
77 | const body = await this.createWithdrawFeeBody({
78 | queryId: params?.queryId,
79 | });
80 |
81 | const value = BigInt(params?.gasAmount ?? this.gasConstants.withdrawFee);
82 |
83 | return { to, body, value };
84 | }
85 |
86 | public async sendWithdrawFee(
87 | provider: ContractProvider,
88 | via: Sender,
89 | params: Parameters[1],
90 | ) {
91 | const txParams = await this.getWithdrawFeeTxParams(provider, params);
92 |
93 | return via.send(txParams);
94 | }
95 |
96 | /**
97 | * Get the current state of the vault contract.
98 | *
99 | * @param {ContractProvider} provider - {@link ContractProvider} instance
100 | *
101 | *
102 | * @returns {Promise} structure containing the current state of the vault contract.
103 | */
104 | public async getVaultData(provider: ContractProvider) {
105 | const result = await provider.get("get_vault_data", []);
106 |
107 | return {
108 | ownerAddress: result.stack.readAddress(),
109 | tokenAddress: result.stack.readAddress(),
110 | routerAddress: result.stack.readAddress(),
111 | depositedAmount: result.stack.readBigNumber(),
112 | };
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/LpAccount/LpAccountV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_VERSION } from "../../constants";
5 | import { LpAccountV2_2 } from "./LpAccountV2_2";
6 |
7 | describe("LpAccountV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(LpAccountV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/LpAccount/LpAccountV2_2.ts:
--------------------------------------------------------------------------------
1 | import { DEX_VERSION } from "../../constants";
2 | import {
3 | LpAccountV2_1,
4 | type LpAccountV2_1Options,
5 | } from "../../v2_1/LpAccount/LpAccountV2_1";
6 |
7 | export interface LpAccountV2_2Options extends LpAccountV2_1Options {}
8 |
9 | export class LpAccountV2_2 extends LpAccountV2_1 {
10 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
11 | }
12 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/index.ts:
--------------------------------------------------------------------------------
1 | import { PtonV2_1 } from "../../pTON/v2_1/PtonV2_1";
2 |
3 | import { BaseRouterV2_2 } from "./router/BaseRouterV2_2";
4 | import { CPIRouterV2_2 } from "./router/CPIRouterV2_2";
5 | import { StableRouterV2_2 } from "./router/StableRouterV2_2";
6 | import { WCPIRouterV2_2 } from "./router/WCPIRouterV2_2";
7 | import { WStableRouterV2_2 } from "./router/WStableRouterV2_2";
8 |
9 | import { BasePoolV2_2 } from "./pool/BasePoolV2_2";
10 | import { CPIPoolV2_2 } from "./pool/CPIPoolV2_2";
11 | import { StablePoolV2_2 } from "./pool/StablePoolV2_2";
12 | import { WCPIPoolV2_2 } from "./pool/WCPIPoolV2_2";
13 | import { WStablePoolV2_2 } from "./pool/WStablePoolV2_2";
14 |
15 | import { VaultV2_2 } from "./vault/VaultV2_2";
16 |
17 | import { LpAccountV2_2 } from "./LpAccount/LpAccountV2_2";
18 |
19 | export { CPIRouterV2_2, StableRouterV2_2, WCPIRouterV2_2, WStableRouterV2_2 };
20 | export { CPIPoolV2_2, StablePoolV2_2, WCPIPoolV2_2, WStablePoolV2_2 };
21 | export { LpAccountV2_2 };
22 | export { VaultV2_2 };
23 |
24 | /** @deprecated. Use explicit Router instead (e.g. DEX.Router.CPI) or use `dexFactory` */
25 | export class RouterV2_2 extends BaseRouterV2_2 {
26 | public static readonly CPI = CPIRouterV2_2;
27 | public static readonly Stable = StableRouterV2_2;
28 | public static readonly WCPI = WCPIRouterV2_2;
29 | public static readonly WStable = WStableRouterV2_2;
30 | }
31 |
32 | /** @deprecated. Use explicit Pool instead (e.g. DEX.Pool.CPI) or use `dexFactory` */
33 | export class PoolV2_2 extends BasePoolV2_2 {
34 | public static readonly CPI = CPIPoolV2_2;
35 | public static readonly Stable = StablePoolV2_2;
36 | public static readonly WCPI = WCPIPoolV2_2;
37 | public static readonly WStable = WStablePoolV2_2;
38 | }
39 |
40 | export const DEX = {
41 | Router: RouterV2_2,
42 | Pool: PoolV2_2,
43 | LpAccount: LpAccountV2_2,
44 | Vault: VaultV2_2,
45 | pTON: PtonV2_1,
46 | } as const;
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/BasePoolV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_VERSION } from "../../constants";
9 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
10 | import { BasePoolV2_2 } from "./BasePoolV2_2";
11 |
12 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
13 | const POOL_ADDRESS = "kQDZVuwLe9I6XjZrnm3txQyEHuV5RcwlSRVHpqiutLsw6HR7"; // TestRED/TestBLUE pool
14 |
15 | describe("BasePoolV2_2", () => {
16 | beforeAll(setup);
17 |
18 | describe("version", () => {
19 | it("should have expected static value", () => {
20 | expect(BasePoolV2_2.version).toBe(DEX_VERSION.v2_2);
21 | });
22 | });
23 |
24 | describe("getLpAccount", () => {
25 | it("should return LpAccount instance with expected version", async () => {
26 | const snapshot = createProviderSnapshot().cell(
27 | "te6cckEBAQEAJAAAQ4AVCq1wcAqyiCdZCD0uy2zlKSKJURP53P8BGQXdhxOazjC3iKkE",
28 | );
29 | const provider = createMockProviderFromSnapshot(snapshot);
30 |
31 | const contract = provider.open(BasePoolV2_2.create(POOL_ADDRESS));
32 |
33 | const lpAccount = await contract.getLpAccount({
34 | ownerAddress: USER_WALLET_ADDRESS,
35 | });
36 |
37 | expect(lpAccount).toBeInstanceOf(LpAccountV2_2);
38 | });
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/BasePoolV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_VERSION } from "../../constants";
5 | import {
6 | BasePoolV2_1,
7 | type BasePoolV2_1Options,
8 | } from "../../v2_1/pool/BasePoolV2_1";
9 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
10 |
11 | export interface BasePoolV2_2Options extends BasePoolV2_1Options {}
12 |
13 | export class BasePoolV2_2 extends BasePoolV2_1 {
14 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
15 |
16 | public override async getLpAccount(
17 | provider: ContractProvider,
18 | params: {
19 | ownerAddress: AddressType;
20 | },
21 | ) {
22 | const lpAccountAddress = await this.getLpAccountAddress(provider, params);
23 |
24 | return LpAccountV2_2.create(lpAccountAddress);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/CPIPoolV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
10 | import { CPIPoolV2_2 } from "./CPIPoolV2_2";
11 |
12 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
13 | const POOL_ADDRESS = "kQDZVuwLe9I6XjZrnm3txQyEHuV5RcwlSRVHpqiutLsw6HR7"; // TestRED/TestBLUE pool
14 |
15 | describe("CPIPoolV2_2", () => {
16 | beforeAll(setup);
17 |
18 | describe("version", () => {
19 | it("should have expected static value", () => {
20 | expect(CPIPoolV2_2.version).toBe(DEX_VERSION.v2_2);
21 | });
22 | });
23 |
24 | describe("dexType", () => {
25 | it("should have expected static value", () => {
26 | expect(CPIPoolV2_2.dexType).toBe(DEX_TYPE.CPI);
27 | });
28 | });
29 |
30 | describe("getLpAccount", () => {
31 | it("should return LpAccount instance with expected version", async () => {
32 | const snapshot = createProviderSnapshot().cell(
33 | "te6cckEBAQEAJAAAQ4AVCq1wcAqyiCdZCD0uy2zlKSKJURP53P8BGQXdhxOazjC3iKkE",
34 | );
35 | const provider = createMockProviderFromSnapshot(snapshot);
36 |
37 | const contract = provider.open(CPIPoolV2_2.create(POOL_ADDRESS));
38 |
39 | const lpAccount = await contract.getLpAccount({
40 | ownerAddress: USER_WALLET_ADDRESS,
41 | });
42 |
43 | expect(lpAccount).toBeInstanceOf(LpAccountV2_2);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/CPIPoolV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_VERSION } from "../../constants";
5 | import { CPIPoolV2_1 } from "../../v2_1/pool/CPIPoolV2_1";
6 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
7 |
8 | export class CPIPoolV2_2 extends CPIPoolV2_1 {
9 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
10 |
11 | public override async getLpAccount(
12 | provider: ContractProvider,
13 | params: {
14 | ownerAddress: AddressType;
15 | },
16 | ) {
17 | const lpAccountAddress = await this.getLpAccountAddress(provider, params);
18 |
19 | return LpAccountV2_2.create(lpAccountAddress);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/StablePoolV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
10 | import { StablePoolV2_2 } from "./StablePoolV2_2";
11 |
12 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
13 | const POOL_ADDRESS = "kQDZVuwLe9I6XjZrnm3txQyEHuV5RcwlSRVHpqiutLsw6HR7"; // TestRED/TestBLUE pool
14 |
15 | describe("StablePoolV2_2", () => {
16 | beforeAll(setup);
17 |
18 | describe("version", () => {
19 | it("should have expected static value", () => {
20 | expect(StablePoolV2_2.version).toBe(DEX_VERSION.v2_2);
21 | });
22 | });
23 |
24 | describe("dexType", () => {
25 | it("should have expected static value", () => {
26 | expect(StablePoolV2_2.dexType).toBe(DEX_TYPE.Stable);
27 | });
28 | });
29 |
30 | describe("getLpAccount", () => {
31 | it("should return LpAccount instance with expected version", async () => {
32 | const snapshot = createProviderSnapshot().cell(
33 | "te6cckEBAQEAJAAAQ4AVCq1wcAqyiCdZCD0uy2zlKSKJURP53P8BGQXdhxOazjC3iKkE",
34 | );
35 | const provider = createMockProviderFromSnapshot(snapshot);
36 |
37 | const contract = provider.open(StablePoolV2_2.create(POOL_ADDRESS));
38 |
39 | const lpAccount = await contract.getLpAccount({
40 | ownerAddress: USER_WALLET_ADDRESS,
41 | });
42 |
43 | expect(lpAccount).toBeInstanceOf(LpAccountV2_2);
44 | });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/StablePoolV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_VERSION } from "../../constants";
5 | import { StablePoolV2_1 } from "../../v2_1/pool/StablePoolV2_1";
6 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
7 |
8 | export class StablePoolV2_2 extends StablePoolV2_1 {
9 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
10 |
11 | public override async getLpAccount(
12 | provider: ContractProvider,
13 | params: {
14 | ownerAddress: AddressType;
15 | },
16 | ) {
17 | const lpAccountAddress = await this.getLpAccountAddress(provider, params);
18 |
19 | return LpAccountV2_2.create(lpAccountAddress);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/WCPIPoolV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WCPIPoolV2_2 } from "./WCPIPoolV2_2";
6 |
7 | describe("WCPIPoolV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WCPIPoolV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WCPIPoolV2_2.dexType).toBe(DEX_TYPE.WCPI);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/WCPIPoolV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_VERSION } from "../../constants";
5 | import { WCPIPoolV2_1 } from "../../v2_1/pool/WCPIPoolV2_1";
6 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
7 |
8 | export class WCPIPoolV2_2 extends WCPIPoolV2_1 {
9 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
10 |
11 | public override async getLpAccount(
12 | provider: ContractProvider,
13 | params: {
14 | ownerAddress: AddressType;
15 | },
16 | ) {
17 | const lpAccountAddress = await this.getLpAccountAddress(provider, params);
18 |
19 | return LpAccountV2_2.create(lpAccountAddress);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/WStablePoolV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WStablePoolV2_2 } from "./WStablePoolV2_2";
6 |
7 | describe("WStablePoolV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WStablePoolV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WStablePoolV2_2.dexType).toBe(DEX_TYPE.WStable);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/pool/WStablePoolV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { DEX_VERSION } from "../../constants";
5 | import { WStablePoolV2_1 } from "../../v2_1/pool/WStablePoolV2_1";
6 | import { LpAccountV2_2 } from "../LpAccount/LpAccountV2_2";
7 |
8 | export class WStablePoolV2_2 extends WStablePoolV2_1 {
9 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
10 |
11 | public override async getLpAccount(
12 | provider: ContractProvider,
13 | params: {
14 | ownerAddress: AddressType;
15 | },
16 | ) {
17 | const lpAccountAddress = await this.getLpAccountAddress(provider, params);
18 |
19 | return LpAccountV2_2.create(lpAccountAddress);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/BaseRouterV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_VERSION } from "../../constants";
9 | import { BasePoolV2_2 } from "../pool/BasePoolV2_2";
10 | import { VaultV2_2 } from "../vault/VaultV2_2";
11 | import { BaseRouterV2_2 } from "./BaseRouterV2_2";
12 |
13 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
14 | const ROUTER_ADDRESS = "kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v";
15 | const OFFER_JETTON_ADDRESS = "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5"; // TestRED
16 | const ASK_JETTON_ADDRESS = "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3"; // TestBLUE
17 |
18 | describe("BaseRouterV2_2", () => {
19 | beforeAll(setup);
20 |
21 | describe("version", () => {
22 | it("should have expected static value", () => {
23 | expect(BaseRouterV2_2.version).toBe(DEX_VERSION.v2_2);
24 | });
25 | });
26 |
27 | describe("getPool", () => {
28 | it("should return Pool instance with expected version", async () => {
29 | const snapshot = createProviderSnapshot().cell(
30 | "te6cckEBAQEAJAAAQ4AbKt2Bb3pHS8bNc829uKGQg9yvKLmEqSKo9NUV1pdmHRDqCLOi",
31 | );
32 | const provider = createMockProviderFromSnapshot(snapshot);
33 |
34 | const contract = provider.open(BaseRouterV2_2.create(ROUTER_ADDRESS));
35 |
36 | const pool = await contract.getPool({
37 | token0: OFFER_JETTON_ADDRESS,
38 | token1: ASK_JETTON_ADDRESS,
39 | });
40 |
41 | expect(pool).toBeInstanceOf(BasePoolV2_2);
42 | });
43 | });
44 |
45 | describe("getVault", () => {
46 | it("should return Vault instance with expected version", async () => {
47 | const snapshot = createProviderSnapshot().cell(
48 | "te6cckEBAQEAJAAAQ4APnbYFk/sHIc85nyovU2iRke85JwO8rxYn6ilYMKhtfzAcWMdK",
49 | );
50 | const provider = createMockProviderFromSnapshot(snapshot);
51 |
52 | const contract = provider.open(BaseRouterV2_2.create(ROUTER_ADDRESS));
53 |
54 | const vault = await contract.getVault({
55 | user: USER_WALLET_ADDRESS,
56 | tokenMinter: OFFER_JETTON_ADDRESS,
57 | });
58 |
59 | expect(vault).toBeInstanceOf(VaultV2_2);
60 | });
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/BaseRouterV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { JettonMinter } from "../../../core/JettonMinter";
5 | import { DEX_VERSION } from "../../constants";
6 | import {
7 | BaseRouterV2_1,
8 | type BaseRouterV2_1Options,
9 | } from "../../v2_1/router/BaseRouterV2_1";
10 | import { BasePoolV2_2 } from "../pool/BasePoolV2_2";
11 | import { VaultV2_2 } from "../vault/VaultV2_2";
12 |
13 | export interface BaseRouterV2_2Options extends BaseRouterV2_1Options {}
14 |
15 | export class BaseRouterV2_2 extends BaseRouterV2_1 {
16 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
17 |
18 | public override async getPool(
19 | provider: ContractProvider,
20 | params: {
21 | token0: AddressType;
22 | token1: AddressType;
23 | },
24 | ) {
25 | const poolAddress = await this.getPoolAddressByJettonMinters(
26 | provider,
27 | params,
28 | );
29 |
30 | return BasePoolV2_2.create(poolAddress);
31 | }
32 |
33 | public override async getVault(
34 | provider: ContractProvider,
35 | params: {
36 | user: AddressType;
37 | tokenMinter: AddressType;
38 | },
39 | ) {
40 | const tokenMinter = provider.open(JettonMinter.create(params.tokenMinter));
41 |
42 | const vaultAddress = await this.getVaultAddress(provider, {
43 | user: params.user,
44 | tokenWallet: await tokenMinter.getWalletAddress(this.address),
45 | });
46 |
47 | return VaultV2_2.create(vaultAddress);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/CPIRouterV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { CPIPoolV2_2 } from "../pool/CPIPoolV2_2";
10 | import { VaultV2_2 } from "../vault/VaultV2_2";
11 | import { CPIRouterV2_2 } from "./CPIRouterV2_2";
12 |
13 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
14 | const ROUTER_ADDRESS = "kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v";
15 | const OFFER_JETTON_ADDRESS = "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5"; // TestRED
16 | const ASK_JETTON_ADDRESS = "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3"; // TestBLUE
17 |
18 | describe("CPIRouterV2_2", () => {
19 | beforeAll(setup);
20 |
21 | describe("version", () => {
22 | it("should have expected static value", () => {
23 | expect(CPIRouterV2_2.version).toBe(DEX_VERSION.v2_2);
24 | });
25 | });
26 |
27 | describe("dexType", () => {
28 | it("should have expected static value", () => {
29 | expect(CPIRouterV2_2.dexType).toBe(DEX_TYPE.CPI);
30 | });
31 | });
32 |
33 | describe("getPool", () => {
34 | it("should return Pool instance with expected version", async () => {
35 | const snapshot = createProviderSnapshot().cell(
36 | "te6cckEBAQEAJAAAQ4AbKt2Bb3pHS8bNc829uKGQg9yvKLmEqSKo9NUV1pdmHRDqCLOi",
37 | );
38 | const provider = createMockProviderFromSnapshot(snapshot);
39 |
40 | const contract = provider.open(CPIRouterV2_2.create(ROUTER_ADDRESS));
41 |
42 | const pool = await contract.getPool({
43 | token0: OFFER_JETTON_ADDRESS,
44 | token1: ASK_JETTON_ADDRESS,
45 | });
46 |
47 | expect(pool).toBeInstanceOf(CPIPoolV2_2);
48 | });
49 | });
50 |
51 | describe("getVault", () => {
52 | it("should return Vault instance with expected version", async () => {
53 | const snapshot = createProviderSnapshot().cell(
54 | "te6cckEBAQEAJAAAQ4APnbYFk/sHIc85nyovU2iRke85JwO8rxYn6ilYMKhtfzAcWMdK",
55 | );
56 | const provider = createMockProviderFromSnapshot(snapshot);
57 |
58 | const contract = provider.open(CPIRouterV2_2.create(ROUTER_ADDRESS));
59 |
60 | const vault = await contract.getVault({
61 | user: USER_WALLET_ADDRESS,
62 | tokenMinter: OFFER_JETTON_ADDRESS,
63 | });
64 |
65 | expect(vault).toBeInstanceOf(VaultV2_2);
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/CPIRouterV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { JettonMinter } from "../../../core/JettonMinter";
5 | import { DEX_VERSION } from "../../constants";
6 | import { CPIRouterV2_1 } from "../../v2_1/router/CPIRouterV2_1";
7 | import { CPIPoolV2_2 } from "../pool/CPIPoolV2_2";
8 | import { VaultV2_2 } from "../vault/VaultV2_2";
9 |
10 | export class CPIRouterV2_2 extends CPIRouterV2_1 {
11 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
12 |
13 | public override async getPool(
14 | provider: ContractProvider,
15 | params: {
16 | token0: AddressType;
17 | token1: AddressType;
18 | },
19 | ) {
20 | const poolAddress = await this.getPoolAddressByJettonMinters(
21 | provider,
22 | params,
23 | );
24 |
25 | return CPIPoolV2_2.create(poolAddress);
26 | }
27 |
28 | public override async getVault(
29 | provider: ContractProvider,
30 | params: {
31 | user: AddressType;
32 | tokenMinter: AddressType;
33 | },
34 | ) {
35 | const tokenMinter = provider.open(JettonMinter.create(params.tokenMinter));
36 |
37 | const vaultAddress = await this.getVaultAddress(provider, {
38 | user: params.user,
39 | tokenWallet: await tokenMinter.getWalletAddress(this.address),
40 | });
41 |
42 | return VaultV2_2.create(vaultAddress);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/StableRouterV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import {
4 | createMockProviderFromSnapshot,
5 | createProviderSnapshot,
6 | setup,
7 | } from "../../../../test-utils";
8 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
9 | import { StablePoolV2_2 } from "../pool/StablePoolV2_2";
10 | import { VaultV2_2 } from "../vault/VaultV2_2";
11 | import { StableRouterV2_2 } from "./StableRouterV2_2";
12 |
13 | const USER_WALLET_ADDRESS = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
14 | const ROUTER_ADDRESS = "kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v";
15 | const OFFER_JETTON_ADDRESS = "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5"; // TestRED
16 | const ASK_JETTON_ADDRESS = "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3"; // TestBLUE
17 |
18 | describe("StableRouterV2_2", () => {
19 | beforeAll(setup);
20 |
21 | describe("version", () => {
22 | it("should have expected static value", () => {
23 | expect(StableRouterV2_2.version).toBe(DEX_VERSION.v2_2);
24 | });
25 | });
26 |
27 | describe("dexType", () => {
28 | it("should have expected static value", () => {
29 | expect(StableRouterV2_2.dexType).toBe(DEX_TYPE.Stable);
30 | });
31 | });
32 |
33 | describe("getPool", () => {
34 | it("should return Pool instance with expected version", async () => {
35 | const snapshot = createProviderSnapshot().cell(
36 | "te6cckEBAQEAJAAAQ4AbKt2Bb3pHS8bNc829uKGQg9yvKLmEqSKo9NUV1pdmHRDqCLOi",
37 | );
38 | const provider = createMockProviderFromSnapshot(snapshot);
39 |
40 | const contract = provider.open(StableRouterV2_2.create(ROUTER_ADDRESS));
41 |
42 | const pool = await contract.getPool({
43 | token0: OFFER_JETTON_ADDRESS,
44 | token1: ASK_JETTON_ADDRESS,
45 | });
46 |
47 | expect(pool).toBeInstanceOf(StablePoolV2_2);
48 | });
49 | });
50 |
51 | describe("getVault", () => {
52 | it("should return Vault instance with expected version", async () => {
53 | const snapshot = createProviderSnapshot().cell(
54 | "te6cckEBAQEAJAAAQ4APnbYFk/sHIc85nyovU2iRke85JwO8rxYn6ilYMKhtfzAcWMdK",
55 | );
56 | const provider = createMockProviderFromSnapshot(snapshot);
57 |
58 | const contract = provider.open(StableRouterV2_2.create(ROUTER_ADDRESS));
59 |
60 | const vault = await contract.getVault({
61 | user: USER_WALLET_ADDRESS,
62 | tokenMinter: OFFER_JETTON_ADDRESS,
63 | });
64 |
65 | expect(vault).toBeInstanceOf(VaultV2_2);
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/StableRouterV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { JettonMinter } from "../../../core/JettonMinter";
5 | import { DEX_VERSION } from "../../constants";
6 | import { StableRouterV2_1 } from "../../v2_1/router/StableRouterV2_1";
7 | import { StablePoolV2_2 } from "../pool/StablePoolV2_2";
8 | import { VaultV2_2 } from "../vault/VaultV2_2";
9 |
10 | export class StableRouterV2_2 extends StableRouterV2_1 {
11 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
12 |
13 | public override async getPool(
14 | provider: ContractProvider,
15 | params: {
16 | token0: AddressType;
17 | token1: AddressType;
18 | },
19 | ) {
20 | const poolAddress = await this.getPoolAddressByJettonMinters(
21 | provider,
22 | params,
23 | );
24 |
25 | return StablePoolV2_2.create(poolAddress);
26 | }
27 |
28 | public override async getVault(
29 | provider: ContractProvider,
30 | params: {
31 | user: AddressType;
32 | tokenMinter: AddressType;
33 | },
34 | ) {
35 | const tokenMinter = provider.open(JettonMinter.create(params.tokenMinter));
36 |
37 | const vaultAddress = await this.getVaultAddress(provider, {
38 | user: params.user,
39 | tokenWallet: await tokenMinter.getWalletAddress(this.address),
40 | });
41 |
42 | return VaultV2_2.create(vaultAddress);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/WCPIRouterV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WCPIRouterV2_2 } from "./WCPIRouterV2_2";
6 |
7 | describe("WCPIRouterV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WCPIRouterV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WCPIRouterV2_2.dexType).toBe(DEX_TYPE.WCPI);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/WCPIRouterV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { JettonMinter } from "../../../core/JettonMinter";
5 | import { DEX_VERSION } from "../../constants";
6 | import { WCPIRouterV2_1 } from "../../v2_1/router/WCPIRouterV2_1";
7 | import { WCPIPoolV2_2 } from "../pool/WCPIPoolV2_2";
8 | import { VaultV2_2 } from "../vault/VaultV2_2";
9 |
10 | export class WCPIRouterV2_2 extends WCPIRouterV2_1 {
11 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
12 |
13 | public override async getPool(
14 | provider: ContractProvider,
15 | params: {
16 | token0: AddressType;
17 | token1: AddressType;
18 | },
19 | ) {
20 | const poolAddress = await this.getPoolAddressByJettonMinters(
21 | provider,
22 | params,
23 | );
24 |
25 | return WCPIPoolV2_2.create(poolAddress);
26 | }
27 |
28 | public override async getVault(
29 | provider: ContractProvider,
30 | params: {
31 | user: AddressType;
32 | tokenMinter: AddressType;
33 | },
34 | ) {
35 | const tokenMinter = provider.open(JettonMinter.create(params.tokenMinter));
36 |
37 | const vaultAddress = await this.getVaultAddress(provider, {
38 | user: params.user,
39 | tokenWallet: await tokenMinter.getWalletAddress(this.address),
40 | });
41 |
42 | return VaultV2_2.create(vaultAddress);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/WStableRouterV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_TYPE, DEX_VERSION } from "../../constants";
5 | import { WStableRouterV2_2 } from "./WStableRouterV2_2";
6 |
7 | describe("WStableRouterV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(WStableRouterV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 |
16 | describe("dexType", () => {
17 | it("should have expected static value", () => {
18 | expect(WStableRouterV2_2.dexType).toBe(DEX_TYPE.WStable);
19 | });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/router/WStableRouterV2_2.ts:
--------------------------------------------------------------------------------
1 | import type { ContractProvider } from "@ton/ton";
2 |
3 | import type { AddressType } from "../../../../types";
4 | import { JettonMinter } from "../../../core/JettonMinter";
5 | import { DEX_VERSION } from "../../constants";
6 | import { WStableRouterV2_1 } from "../../v2_1/router/WStableRouterV2_1";
7 | import { WStablePoolV2_2 } from "../pool/WStablePoolV2_2";
8 | import { VaultV2_2 } from "../vault/VaultV2_2";
9 |
10 | export class WStableRouterV2_2 extends WStableRouterV2_1 {
11 | public static override readonly version: DEX_VERSION = DEX_VERSION.v2_2;
12 |
13 | public override async getPool(
14 | provider: ContractProvider,
15 | params: {
16 | token0: AddressType;
17 | token1: AddressType;
18 | },
19 | ) {
20 | const poolAddress = await this.getPoolAddressByJettonMinters(
21 | provider,
22 | params,
23 | );
24 |
25 | return WStablePoolV2_2.create(poolAddress);
26 | }
27 |
28 | public override async getVault(
29 | provider: ContractProvider,
30 | params: {
31 | user: AddressType;
32 | tokenMinter: AddressType;
33 | },
34 | ) {
35 | const tokenMinter = provider.open(JettonMinter.create(params.tokenMinter));
36 |
37 | const vaultAddress = await this.getVaultAddress(provider, {
38 | user: params.user,
39 | tokenWallet: await tokenMinter.getWalletAddress(this.address),
40 | });
41 |
42 | return VaultV2_2.create(vaultAddress);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/vault/VaultV2_2.test.ts:
--------------------------------------------------------------------------------
1 | import { beforeAll, describe, expect, it } from "vitest";
2 |
3 | import { setup } from "../../../../test-utils";
4 | import { DEX_VERSION } from "../../constants";
5 | import { VaultV2_2 } from "./VaultV2_2";
6 |
7 | describe("VaultV2_2", () => {
8 | beforeAll(setup);
9 |
10 | describe("version", () => {
11 | it("should have expected static value", () => {
12 | expect(VaultV2_2.version).toBe(DEX_VERSION.v2_2);
13 | });
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/dex/v2_2/vault/VaultV2_2.ts:
--------------------------------------------------------------------------------
1 | import { DEX_VERSION } from "../../constants";
2 | import { VaultV2_1, type VaultV2_1Options } from "../../v2_1/vault/VaultV2_1";
3 |
4 | export interface VaultV2_2Options extends VaultV2_1Options {}
5 |
6 | /**
7 | * Token vault stores referral fees on a separate contract similar to an LP account.
8 | * This will allow us to decrease TX fees for swaps since users won't have to pay for additional Jetton transfer TX.
9 | *
10 | * Vault address is defined by router_address, owner_address and router_token_Wallet_address,
11 | * so, for each token, each user can have a dedicated vault contract.
12 | */
13 |
14 | export class VaultV2_2 extends VaultV2_1 {
15 | public static override readonly version = DEX_VERSION.v2_2;
16 | }
17 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/constants.ts:
--------------------------------------------------------------------------------
1 | export const FARM_OP_CODES = {
2 | STAKE: 0x6ec9dc65,
3 | CLAIM_REWARDS: 0x78d9f109,
4 | UNSTAKE: 0xb92965a0,
5 | } as const;
6 |
7 | export const FARM_VERSION = {
8 | v1: "v1",
9 | v2: "v2",
10 | v3: "v3",
11 | } as const;
12 |
13 | export type FARM_VERSION = (typeof FARM_VERSION)[keyof typeof FARM_VERSION];
14 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/index.ts:
--------------------------------------------------------------------------------
1 | import { FARM_VERSION } from "./constants";
2 | import { FARM as FARMv1 } from "./v1";
3 | import { FARM as FARMv2 } from "./v2";
4 | import { FARM as FARMv3 } from "./v3";
5 |
6 | export { FARM_VERSION } from "./constants";
7 |
8 | export const FARM = {
9 | [FARM_VERSION.v1]: FARMv1,
10 | [FARM_VERSION.v2]: FARMv2,
11 | [FARM_VERSION.v3]: FARMv3,
12 | } as const;
13 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/v1/FarmNftItemV1.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Cell,
3 | type ContractProvider,
4 | type Sender,
5 | type SenderArguments,
6 | beginCell,
7 | toNano,
8 | } from "@ton/ton";
9 |
10 | import type { AddressType, AmountType, QueryIdType } from "../../../types";
11 | import { Contract, type ContractOptions } from "../../core/Contract";
12 | import { FARM_OP_CODES, FARM_VERSION } from "../constants";
13 |
14 | export interface FarmNftItemV1Options extends ContractOptions {
15 | gasConstants?: Partial;
16 | }
17 |
18 | /**
19 | * @deprecated `v1` version of the FarmNftItem contracts is deprecated.
20 | *
21 | * Only use this version to claim rewards and unstake tokens from the contract.
22 | * For all other operations, use the latest version of the contract.
23 | */
24 | export class FarmNftItemV1 extends Contract {
25 | public static readonly version: FARM_VERSION = FARM_VERSION.v1;
26 |
27 | public static readonly gasConstants = {
28 | claimRewards: toNano("0.3"),
29 | unstake: toNano("0.4"),
30 | destroy: toNano("0.05"),
31 | };
32 |
33 | public readonly gasConstants;
34 |
35 | constructor(
36 | address: AddressType,
37 | { gasConstants, ...options }: FarmNftItemV1Options = {},
38 | ) {
39 | super(address, options);
40 |
41 | this.gasConstants = {
42 | ...FarmNftItemV1.gasConstants,
43 | ...gasConstants,
44 | };
45 | }
46 |
47 | public async createClaimRewardsBody(params?: {
48 | queryId?: QueryIdType;
49 | }): Promise {
50 | return beginCell()
51 | .storeUint(FARM_OP_CODES.CLAIM_REWARDS, 32)
52 | .storeUint(BigInt(params?.queryId ?? 0), 64)
53 | .endCell();
54 | }
55 |
56 | /**
57 | * Build all data required to execute a `claim_rewards` transaction.
58 | *
59 | * @param {bigint | number | string | undefined} params.gasAmount - Optional; Custom transaction gas amount (in nanoTons)
60 | * @param {bigint | number | undefined} params.queryId - Optional; query id
61 | *
62 | * @returns {SenderArguments} all data required to execute a `claim_rewards` transaction.
63 | */
64 | public async getClaimRewardsTxParams(
65 | provider: ContractProvider,
66 | params?: {
67 | gasAmount?: AmountType;
68 | queryId?: QueryIdType;
69 | },
70 | ): Promise {
71 | const to = this.address;
72 |
73 | const body = await this.createClaimRewardsBody({
74 | queryId: params?.queryId,
75 | });
76 |
77 | const value = BigInt(params?.gasAmount ?? this.gasConstants.claimRewards);
78 |
79 | return { to, value, body };
80 | }
81 |
82 | public async sendClaimRewards(
83 | provider: ContractProvider,
84 | via: Sender,
85 | params: Parameters[1],
86 | ) {
87 | const txParams = await this.getClaimRewardsTxParams(provider, params);
88 |
89 | return via.send(txParams);
90 | }
91 |
92 | public async createUnstakeBody(params?: {
93 | queryId?: QueryIdType;
94 | }): Promise {
95 | return beginCell()
96 | .storeUint(FARM_OP_CODES.UNSTAKE, 32)
97 | .storeUint(BigInt(params?.queryId ?? 0), 64)
98 | .endCell();
99 | }
100 |
101 | /**
102 | * Build all data required to execute a `unstake` transaction.
103 | *
104 | * @param {bigint | number | string | undefined} params.gasAmount - Optional; Custom transaction gas amount (in nanoTons)
105 | * @param {bigint | number | undefined} params.queryId - Optional; query id
106 | *
107 | * @returns {SenderArguments} all data required to execute a `unstake` transaction.
108 | */
109 | public async getUnstakeTxParams(
110 | provider: ContractProvider,
111 | params?: {
112 | gasAmount?: AmountType;
113 | queryId?: QueryIdType;
114 | },
115 | ): Promise {
116 | const to = this.address;
117 |
118 | const body = await this.createUnstakeBody({
119 | queryId: params?.queryId,
120 | });
121 |
122 | const value = BigInt(params?.gasAmount ?? this.gasConstants.unstake);
123 |
124 | return { to, value, body };
125 | }
126 |
127 | public async sendUnstake(
128 | provider: ContractProvider,
129 | via: Sender,
130 | params: Parameters[1],
131 | ) {
132 | const txParams = await this.getUnstakeTxParams(provider, params);
133 |
134 | return via.send(txParams);
135 | }
136 |
137 | /**
138 | * @returns structure containing current state of the farm NFT
139 | *
140 | * @property {number} status Status of the contract: uninitialized `0`, active `1`, unstaked `2`, claiming `3`
141 | * @property {boolean} isSoulbound If nft is soulbound
142 | * @property {bigint} stakedTokens Amount of staked tokens
143 | * @property {bigint} claimedPerUnitNanorewards `accrued_per_unit_nanorewards` at the time the user made the stake or last claimed rewards
144 | */
145 | public async getFarmingData(provider: ContractProvider) {
146 | const result = await provider.get("get_farming_data", []);
147 |
148 | return {
149 | status: result.stack.readNumber(),
150 | isSoulbound: result.stack.readBoolean(),
151 | stakedTokens: result.stack.readBigNumber(),
152 | claimedPerUnitNanorewards: result.stack.readBigNumber(),
153 | };
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/v1/index.ts:
--------------------------------------------------------------------------------
1 | import { FarmNftItemV1 } from "./FarmNftItemV1";
2 | import { FarmNftMinterV1 } from "./FarmNftMinterV1";
3 |
4 | export const FARM = {
5 | NftItem: FarmNftItemV1,
6 | NftMinter: FarmNftMinterV1,
7 | } as const;
8 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/v2/FarmNftItemV2.ts:
--------------------------------------------------------------------------------
1 | import type { Cell, ContractProvider, Sender, SenderArguments } from "@ton/ton";
2 |
3 | import type { AmountType, QueryIdType } from "../../../types";
4 | import { createSbtDestroyMessage } from "../../../utils/createSbtDestroyMessage";
5 | import { FARM_VERSION } from "../constants";
6 | import { FarmNftItemV1, type FarmNftItemV1Options } from "../v1/FarmNftItemV1";
7 |
8 | export interface FarmNftItemV2Options extends FarmNftItemV1Options {}
9 |
10 | /**
11 | * @deprecated `v2` version of the FarmNftItem contracts is deprecated.
12 | *
13 | * Only use this version to claim rewards and unstake tokens from the contract.
14 | * For all other operations, use the latest version of the contract.
15 | */
16 | export class FarmNftItemV2 extends FarmNftItemV1 {
17 | public static override version = FARM_VERSION.v2;
18 |
19 | public async createDestroyBody(params?: {
20 | queryId?: QueryIdType;
21 | }): Promise {
22 | return createSbtDestroyMessage({
23 | queryId: params?.queryId ?? 0,
24 | });
25 | }
26 |
27 | /**
28 | * Build all data required to execute a `destroy` transaction.
29 | *
30 | * @param {bigint | number | string | undefined} params.gasAmount - Optional; amount of gas for the transaction (in nanoTons)
31 | * @param {bigint | number | undefined} params.queryId - Optional; query id
32 | *
33 | * @returns {SenderArguments} all data required to execute a `destroy` transaction.
34 | */
35 | public async getDestroyTxParams(
36 | provider: ContractProvider,
37 | params?: {
38 | gasAmount?: AmountType;
39 | queryId?: QueryIdType;
40 | },
41 | ): Promise {
42 | const to = this.address;
43 |
44 | const body = await this.createDestroyBody({
45 | queryId: params?.queryId,
46 | });
47 |
48 | const value = BigInt(params?.gasAmount ?? this.gasConstants.destroy);
49 |
50 | return { to, value, body };
51 | }
52 |
53 | public async sendDestroy(
54 | provider: ContractProvider,
55 | via: Sender,
56 | params: Parameters[1],
57 | ) {
58 | const txParams = await this.getDestroyTxParams(provider, params);
59 |
60 | return via.send(txParams);
61 | }
62 |
63 | /**
64 | * @returns structure containing current state of the farm NFT
65 | *
66 | * @property {number} status Status of the contract: uninitialized `0`, active `1`, unstaked `2`, claiming `3`
67 | * @property {bigint} revokeTime Timestamp of unstake
68 | * @property {bigint} stakedTokens Amount of staked tokens
69 | * @property {bigint} claimedPerUnitNanorewards `accrued_per_unit_nanorewards` at the time the user made the stake or last claimed rewards
70 | * @property {bigint} stakeDate Timestamp in which the owner started staking
71 | * @property {boolean} isSoulbound If nft is soulbound; Always true in V2
72 | */
73 | public override async getFarmingData(provider: ContractProvider) {
74 | const result = await provider.get("get_farming_data", []);
75 |
76 | return {
77 | status: result.stack.readNumber(),
78 | revokeTime: result.stack.readBigNumber(),
79 | stakedTokens: result.stack.readBigNumber(),
80 | claimedPerUnitNanorewards: result.stack.readBigNumber(),
81 | stakeDate: result.stack.readBigNumber(),
82 | isSoulbound: true, // NFTs are always soulbound in V2
83 | };
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/v2/index.ts:
--------------------------------------------------------------------------------
1 | import { FarmNftItemV2 } from "./FarmNftItemV2";
2 | import { FarmNftMinterV2 } from "./FarmNftMinterV2";
3 |
4 | export const FARM = {
5 | NftItem: FarmNftItemV2,
6 | NftMinter: FarmNftMinterV2,
7 | } as const;
8 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/farm/v3/index.ts:
--------------------------------------------------------------------------------
1 | import { FarmNftItemV3 } from "./FarmNftItemV3";
2 | import { FarmNftMinterV3 } from "./FarmNftMinterV3";
3 |
4 | export const FARM = {
5 | NftItem: FarmNftItemV3,
6 | NftMinter: FarmNftMinterV3,
7 | } as const;
8 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/AbstractPton.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Address,
3 | Cell,
4 | ContractProvider,
5 | SenderArguments,
6 | } from "@ton/ton";
7 |
8 | import type { AddressType, AmountType, QueryIdType } from "../../types";
9 | import type { pTON_VERSION } from "./constants";
10 |
11 | export interface AbstractPton {
12 | version: pTON_VERSION;
13 | address: Address;
14 |
15 | getTonTransferTxParams(
16 | provider: ContractProvider,
17 | params: {
18 | tonAmount: AmountType;
19 | destinationAddress: AddressType;
20 | refundAddress: AddressType;
21 | forwardPayload?: Cell;
22 | forwardTonAmount?: AmountType;
23 | queryId?: QueryIdType;
24 | },
25 | ): Promise;
26 | }
27 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/constants.ts:
--------------------------------------------------------------------------------
1 | export const pTON_VERSION = {
2 | v1: "v1",
3 | v2_1: "v2_1",
4 | } as const;
5 |
6 | export type pTON_VERSION = (typeof pTON_VERSION)[keyof typeof pTON_VERSION];
7 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/index.ts:
--------------------------------------------------------------------------------
1 | import { pTON_VERSION } from "./constants";
2 | import { PtonV1 } from "./v1/PtonV1";
3 | import { PtonV2_1 } from "./v2_1/PtonV2_1";
4 |
5 | export { pTON_VERSION } from "./constants";
6 |
7 | export const pTON = {
8 | [pTON_VERSION.v1]: PtonV1,
9 | [pTON_VERSION.v2_1]: PtonV2_1,
10 | };
11 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/v1/PtonV1.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Cell,
3 | type ContractProvider,
4 | type Sender,
5 | type SenderArguments,
6 | address,
7 | beginCell,
8 | toNano,
9 | } from "@ton/ton";
10 |
11 | import type { AddressType, AmountType, QueryIdType } from "../../../types";
12 | import { createJettonTransferMessage } from "../../../utils/createJettonTransferMessage";
13 | import { toAddress } from "../../../utils/toAddress";
14 | import type { ContractOptions } from "../../core/Contract";
15 | import { JettonMinter } from "../../core/JettonMinter";
16 | import type { AbstractPton } from "../AbstractPton";
17 | import { pTON_VERSION } from "../constants";
18 | import { pTON_OP_CODES } from "./constants";
19 |
20 | export interface PtonV1Options extends ContractOptions {
21 | gasConstants?: Partial;
22 | }
23 |
24 | export class PtonV1 extends JettonMinter implements AbstractPton {
25 | public static readonly version: pTON_VERSION = pTON_VERSION.v1;
26 |
27 | public static readonly address = address(
28 | "EQCM3B12QK1e4yZSf8GtBRT0aLMNyEsBc_DhVfRRtOEffLez",
29 | );
30 |
31 | public static readonly gasConstants = {
32 | deployWallet: toNano("1.05"),
33 | };
34 |
35 | public readonly version = PtonV1.version;
36 |
37 | public readonly gasConstants;
38 |
39 | constructor(
40 | address: AddressType = PtonV1.address,
41 | { gasConstants, ...options }: PtonV1Options = {},
42 | ) {
43 | super(address, options);
44 |
45 | this.gasConstants = {
46 | ...PtonV1.gasConstants,
47 | ...gasConstants,
48 | };
49 | }
50 |
51 | public async getTonTransferTxParams(
52 | provider: ContractProvider,
53 | params: {
54 | tonAmount: AmountType;
55 | destinationAddress: AddressType;
56 | refundAddress: AddressType;
57 | forwardPayload?: Cell;
58 | forwardTonAmount?: AmountType;
59 | queryId?: QueryIdType;
60 | },
61 | ): Promise {
62 | const to = await this.getWalletAddress(provider, params.destinationAddress);
63 |
64 | const body = createJettonTransferMessage({
65 | queryId: params.queryId ?? 0,
66 | amount: params.tonAmount,
67 | destination: params.destinationAddress,
68 | forwardTonAmount: BigInt(params.forwardTonAmount ?? 0),
69 | forwardPayload: params.forwardPayload,
70 | });
71 |
72 | const value =
73 | BigInt(params.tonAmount) + BigInt(params.forwardTonAmount ?? 0);
74 |
75 | return { to, value, body };
76 | }
77 |
78 | public async sendTonTransfer(
79 | provider: ContractProvider,
80 | via: Sender,
81 | params: Parameters[1],
82 | ) {
83 | const txParams = await this.getTonTransferTxParams(provider, params);
84 |
85 | return via.send(txParams);
86 | }
87 |
88 | public async createDeployWalletBody(params: {
89 | ownerAddress: AddressType;
90 | queryId?: QueryIdType;
91 | }): Promise {
92 | return beginCell()
93 | .storeUint(pTON_OP_CODES.DEPLOY_WALLET, 32)
94 | .storeUint(params.queryId ?? 0, 64)
95 | .storeAddress(toAddress(params.ownerAddress))
96 | .endCell();
97 | }
98 |
99 | public async getDeployWalletTxParams(
100 | provider: ContractProvider,
101 | params: {
102 | ownerAddress: AddressType;
103 | gasAmount?: AmountType;
104 | queryId?: QueryIdType;
105 | },
106 | ): Promise {
107 | const to = this.address;
108 |
109 | const body = await this.createDeployWalletBody({
110 | ownerAddress: params.ownerAddress,
111 | queryId: params?.queryId,
112 | });
113 |
114 | const value = BigInt(params?.gasAmount ?? this.gasConstants.deployWallet);
115 |
116 | return { to, value, body };
117 | }
118 |
119 | public async sendDeployWallet(
120 | provider: ContractProvider,
121 | via: Sender,
122 | params: Parameters[1],
123 | ) {
124 | const txParams = await this.getDeployWalletTxParams(provider, params);
125 |
126 | return via.send(txParams);
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/v1/constants.ts:
--------------------------------------------------------------------------------
1 | export const pTON_OP_CODES = {
2 | DEPLOY_WALLET: 0x6cc43573,
3 | } as const;
4 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/v2_1/PtonV2_1.ts:
--------------------------------------------------------------------------------
1 | import {
2 | type Cell,
3 | type ContractProvider,
4 | type Sender,
5 | type SenderArguments,
6 | beginCell,
7 | toNano,
8 | } from "@ton/ton";
9 |
10 | import type { AddressType, AmountType, QueryIdType } from "../../../types";
11 | import { toAddress } from "../../../utils/toAddress";
12 | import type { AbstractPton } from "../AbstractPton";
13 | import { pTON_VERSION } from "../constants";
14 | import { PtonV1, type PtonV1Options } from "../v1/PtonV1";
15 | import { pTON_OP_CODES } from "./constants";
16 |
17 | export interface PtonV2_1Options extends PtonV1Options {
18 | gasConstants?: Partial;
19 | }
20 |
21 | export class PtonV2_1 extends PtonV1 implements AbstractPton {
22 | public static override readonly version = pTON_VERSION.v2_1;
23 |
24 | public static override readonly gasConstants = {
25 | tonTransfer: toNano("0.01"),
26 | deployWallet: toNano("0.1"),
27 | };
28 |
29 | public override readonly version = PtonV2_1.version;
30 |
31 | public override readonly gasConstants;
32 |
33 | constructor(
34 | address: AddressType,
35 | { gasConstants, ...options }: PtonV2_1Options = {},
36 | ) {
37 | super(address, options);
38 |
39 | this.gasConstants = {
40 | ...PtonV2_1.gasConstants,
41 | ...gasConstants,
42 | };
43 | }
44 |
45 | public async createTonTransferBody(params: {
46 | tonAmount: AmountType;
47 | refundAddress: AddressType;
48 | forwardPayload?: Cell;
49 | queryId?: QueryIdType;
50 | }): Promise {
51 | const builder = beginCell();
52 |
53 | builder.storeUint(pTON_OP_CODES.TON_TRANSFER, 32);
54 | builder.storeUint(params.queryId ?? 0, 64);
55 | builder.storeCoins(BigInt(params.tonAmount));
56 | builder.storeAddress(toAddress(params.refundAddress));
57 |
58 | if (params.forwardPayload) {
59 | builder.storeBit(true);
60 | builder.storeRef(params.forwardPayload);
61 | }
62 |
63 | return builder.endCell();
64 | }
65 |
66 | public override async getTonTransferTxParams(
67 | provider: ContractProvider,
68 | params: {
69 | tonAmount: AmountType;
70 | destinationAddress: AddressType;
71 | refundAddress: AddressType;
72 | forwardPayload?: Cell;
73 | forwardTonAmount?: AmountType;
74 | queryId?: QueryIdType;
75 | },
76 | ): Promise {
77 | const to = await this.getWalletAddress(provider, params.destinationAddress);
78 |
79 | const body = await this.createTonTransferBody({
80 | tonAmount: params.tonAmount,
81 | refundAddress: params.refundAddress,
82 | forwardPayload: params.forwardPayload,
83 | queryId: params.queryId,
84 | });
85 |
86 | const value =
87 | BigInt(params.tonAmount) +
88 | BigInt(params.forwardTonAmount ?? 0) +
89 | BigInt(this.gasConstants.tonTransfer);
90 |
91 | return { to, value, body };
92 | }
93 |
94 | public override async sendTonTransfer(
95 | provider: ContractProvider,
96 | via: Sender,
97 | params: Parameters[1],
98 | ) {
99 | const txParams = await this.getTonTransferTxParams(provider, params);
100 |
101 | return via.send(txParams);
102 | }
103 |
104 | public override async createDeployWalletBody(params: {
105 | ownerAddress: AddressType;
106 | excessAddress: AddressType;
107 | queryId?: QueryIdType;
108 | }): Promise {
109 | return beginCell()
110 | .storeUint(pTON_OP_CODES.DEPLOY_WALLET, 32)
111 | .storeUint(params.queryId ?? 0, 64)
112 | .storeAddress(toAddress(params.ownerAddress))
113 | .storeAddress(toAddress(params.excessAddress))
114 | .endCell();
115 | }
116 |
117 | public override async getDeployWalletTxParams(
118 | provider: ContractProvider,
119 | params: {
120 | ownerAddress: AddressType;
121 | excessAddress: AddressType;
122 | gasAmount?: AmountType;
123 | queryId?: QueryIdType;
124 | },
125 | ): Promise {
126 | const to = this.address;
127 |
128 | const body = await this.createDeployWalletBody({
129 | ownerAddress: params.ownerAddress,
130 | excessAddress: params.excessAddress,
131 | queryId: params?.queryId,
132 | });
133 |
134 | const value = BigInt(params?.gasAmount ?? this.gasConstants.deployWallet);
135 |
136 | return { to, value, body };
137 | }
138 |
139 | public override async sendDeployWallet(
140 | provider: ContractProvider,
141 | via: Sender,
142 | params: Parameters[1],
143 | ) {
144 | const txParams = await this.getDeployWalletTxParams(provider, params);
145 |
146 | return via.send(txParams);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/packages/sdk/src/contracts/pTON/v2_1/constants.ts:
--------------------------------------------------------------------------------
1 | export const pTON_OP_CODES = {
2 | TON_TRANSFER: 0x01f3835d,
3 | DEPLOY_WALLET: 0x4f5f4313,
4 | } as const;
5 |
--------------------------------------------------------------------------------
/packages/sdk/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./client";
2 | export * from "./contracts/dex";
3 | export * from "./contracts/farm";
4 | export * from "./contracts/pTON";
5 | export * from "./helpers";
6 | export * from "./types";
7 |
--------------------------------------------------------------------------------
/packages/sdk/src/test-utils/createMockObj.ts:
--------------------------------------------------------------------------------
1 | type DeepPartialAny = Partial<{
2 | [P in keyof T]: T[P] extends object ? DeepPartialAny : unknown;
3 | }>;
4 |
5 | export function createMockObj(obj: DeepPartialAny = {}): T {
6 | return {
7 | ...obj,
8 | } as T;
9 | }
10 |
--------------------------------------------------------------------------------
/packages/sdk/src/test-utils/index.ts:
--------------------------------------------------------------------------------
1 | import { Address } from "@ton/ton";
2 |
3 | export function setup() {
4 | // @ts-expect-error - we are defining a new method on the prototype
5 | // that doesn't exist on the original class
6 | // This is needed for correct Address presentation in snapshots
7 | Address.prototype.toJSON = function () {
8 | return this.toString();
9 | };
10 | }
11 |
12 | export { createMockObj } from "./createMockObj";
13 | export {
14 | createProviderSnapshot,
15 | createMockProvider,
16 | createMockProviderFromSnapshot,
17 | createPrintableProvider,
18 | } from "./snapshot";
19 |
--------------------------------------------------------------------------------
/packages/sdk/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { Address } from "@ton/ton";
2 |
3 | export type AddressType = Address | string;
4 | export type AmountType = bigint | number | string;
5 | export type QueryIdType = number | bigint;
6 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/createJettonTransferMessage.test.ts:
--------------------------------------------------------------------------------
1 | import { address, beginCell } from "@ton/ton";
2 | import { describe, expect, it } from "vitest";
3 |
4 | import { createJettonTransferMessage } from "./createJettonTransferMessage";
5 |
6 | describe("createJettonTransferMessage", () => {
7 | const queryId = 1;
8 | const amount = BigInt("1000000000");
9 | const destination = "EQB3YmWW5ZLhe2gPUAw550e2doyWnkj5hzv3TXp2ekpAWe7v";
10 | const forwardTonAmount = BigInt("500000000");
11 |
12 | it("should create message with expected content with all required fields", async () => {
13 | const message = await createJettonTransferMessage({
14 | queryId,
15 | amount,
16 | destination,
17 | forwardTonAmount,
18 | });
19 |
20 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
21 | '"te6cckEBAQEAOQAAbQ+KfqUAAAAAAAAAAUO5rKAIAO7Eyy3LJcL20B6gGHPOj2ztGS08kfMOd+6a9Oz0lICyEHc1lAGwz8AH"',
22 | );
23 | });
24 |
25 | const customPayload = beginCell().storeUint(1, 32).endCell();
26 |
27 | it("should create message with expected content when customPayload is defined", async () => {
28 | const message = await createJettonTransferMessage({
29 | queryId,
30 | amount,
31 | destination,
32 | forwardTonAmount,
33 | customPayload,
34 | });
35 |
36 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
37 | '"te6cckEBAgEAQAABbQ+KfqUAAAAAAAAAAUO5rKAIAO7Eyy3LJcL20B6gGHPOj2ztGS08kfMOd+6a9Oz0lICyUHc1lAEBAAgAAAABFDJIHA=="',
38 | );
39 | });
40 |
41 | const forwardPayload = beginCell().storeUint(2, 32).endCell();
42 |
43 | it("should create message with expected content when forwardPayload is defined", async () => {
44 | const message = await createJettonTransferMessage({
45 | queryId,
46 | amount,
47 | destination,
48 | forwardTonAmount,
49 | forwardPayload,
50 | });
51 |
52 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
53 | '"te6cckEBAgEAQAABbQ+KfqUAAAAAAAAAAUO5rKAIAO7Eyy3LJcL20B6gGHPOj2ztGS08kfMOd+6a9Oz0lICyEHc1lAMBAAgAAAAC4lvH+Q=="',
54 | );
55 | });
56 |
57 | const responseDestination = address(
58 | "EQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaB3i",
59 | );
60 |
61 | it("should create message with expected content when responseDestination is defined", async () => {
62 | const message = await createJettonTransferMessage({
63 | queryId,
64 | amount,
65 | destination,
66 | forwardTonAmount,
67 | responseDestination,
68 | });
69 |
70 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
71 | '"te6cckEBAQEAWgAAsA+KfqUAAAAAAAAAAUO5rKAIAO7Eyy3LJcL20B6gGHPOj2ztGS08kfMOd+6a9Oz0lICzAAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D/8noARLOaCDuaygBjL74c"',
72 | );
73 | });
74 |
75 | it("should create message with expected content when all fields are defined", async () => {
76 | const message = await createJettonTransferMessage({
77 | queryId,
78 | amount,
79 | destination,
80 | forwardTonAmount,
81 | responseDestination,
82 | customPayload,
83 | forwardPayload,
84 | });
85 |
86 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
87 | '"te6cckEBAwEAaAACsA+KfqUAAAAAAAAAAUO5rKAIAO7Eyy3LJcL20B6gGHPOj2ztGS08kfMOd+6a9Oz0lICzAAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D/8noARLOaKDuaygEBAgAIAAAAAQAIAAAAAiWo6pY="',
88 | );
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/createJettonTransferMessage.ts:
--------------------------------------------------------------------------------
1 | import { type Cell, beginCell } from "@ton/ton";
2 |
3 | import type { AddressType, AmountType, QueryIdType } from "../types";
4 | import { toAddress } from "./toAddress";
5 |
6 | /**
7 | * Implements `transfer` function from Jettons Standard.
8 | * [Docs](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#1-transfer)
9 | *
10 | * ```TL-B
11 | * transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress response_destination:MsgAddress custom_payload:(Maybe ^Cell) forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell) = InternalMsgBody;
12 | * ```
13 | */
14 | export function createJettonTransferMessage(params: {
15 | queryId: QueryIdType;
16 | amount: AmountType;
17 | destination: AddressType;
18 | responseDestination?: AddressType;
19 | customPayload?: Cell;
20 | forwardTonAmount: AmountType;
21 | forwardPayload?: Cell;
22 | }) {
23 | const builder = beginCell();
24 |
25 | builder.storeUint(0xf8a7ea5, 32);
26 | builder.storeUint(params.queryId, 64);
27 | builder.storeCoins(BigInt(params.amount));
28 | builder.storeAddress(toAddress(params.destination));
29 | builder.storeAddress(
30 | params.responseDestination
31 | ? toAddress(params.responseDestination)
32 | : undefined,
33 | );
34 |
35 | if (params.customPayload) {
36 | builder.storeBit(true);
37 | builder.storeRef(params.customPayload);
38 | } else {
39 | builder.storeBit(false);
40 | }
41 |
42 | builder.storeCoins(BigInt(params.forwardTonAmount));
43 |
44 | if (params.forwardPayload) {
45 | builder.storeBit(true);
46 | builder.storeRef(params.forwardPayload);
47 | } else {
48 | builder.storeBit(false);
49 | }
50 |
51 | return builder.endCell();
52 | }
53 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/createSbtDestroyMessage.test.ts:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 |
3 | import { createSbtDestroyMessage } from "./createSbtDestroyMessage";
4 |
5 | describe("createSbtDestroyMessage", () => {
6 | it("should create message", async () => {
7 | const message = await createSbtDestroyMessage();
8 |
9 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
10 | '"te6cckEBAQEADgAAGB8EU3oAAAAAAAAAAOpSrEg="',
11 | );
12 | });
13 |
14 | it("should create message when queryId is defined", async () => {
15 | const queryId = 12345;
16 |
17 | const message = await createSbtDestroyMessage({
18 | queryId,
19 | });
20 |
21 | expect(message.toBoc().toString("base64")).toMatchInlineSnapshot(
22 | '"te6cckEBAQEADgAAGB8EU3oAAAAAAAAwORTSs0A="',
23 | );
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/createSbtDestroyMessage.ts:
--------------------------------------------------------------------------------
1 | import { beginCell } from "@ton/ton";
2 |
3 | import type { QueryIdType } from "../types";
4 |
5 | /**
6 | * Implements `destroy` function from SBT Standard.
7 | * [Docs](https://github.com/ton-blockchain/TEPs/blob/master/text/0085-sbt-standard.md#3-destroy)
8 | *
9 | * ```TL-B
10 | * destroy#1f04537a query_id:uint64 = InternalMsgBody;
11 | * ```
12 | */
13 | export function createSbtDestroyMessage(params?: { queryId: QueryIdType }) {
14 | return beginCell()
15 | .storeUint(0x1f04537a, 32)
16 | .storeUint(params?.queryId ?? 0, 64)
17 | .endCell();
18 | }
19 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/toAddress.test.ts:
--------------------------------------------------------------------------------
1 | import { Address, address } from "@ton/ton";
2 | import { describe, expect, it } from "vitest";
3 |
4 | import { toAddress } from "./toAddress";
5 |
6 | const TEST_ADDRESS_STR = "UQAQnxLqlX2B6w4jQzzzPWA8eyWZVZBz6Y0D_8noARLOaEAn";
7 |
8 | describe("toAddress", () => {
9 | it("should return Address instance if Address instance is passed", () => {
10 | const addressSource = address(TEST_ADDRESS_STR);
11 |
12 | expect(toAddress(addressSource)).toBe(addressSource);
13 | });
14 |
15 | it("should return Address instance if string is passed", () => {
16 | const addressSource = TEST_ADDRESS_STR;
17 |
18 | const result = toAddress(addressSource);
19 |
20 | expect(result).toBeInstanceOf(Address);
21 | expect(result.toString()).toBe(address(TEST_ADDRESS_STR).toString());
22 | });
23 |
24 | it("should return Address instance by converting passed address to string", () => {
25 | const addressSource = { toString: () => TEST_ADDRESS_STR };
26 |
27 | // @ts-expect-error - we are passing non-Address instance here to test the edge case
28 | // bui it should work as expected because we are using `toString` method
29 | const result = toAddress(addressSource);
30 |
31 | expect(result).toBeInstanceOf(Address);
32 | expect(result.toString()).toBe(address(TEST_ADDRESS_STR).toString());
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/packages/sdk/src/utils/toAddress.ts:
--------------------------------------------------------------------------------
1 | import { Address, address } from "@ton/ton";
2 |
3 | import type { AddressType } from "../types";
4 |
5 | /** Convert passed value to Address instance if it's not already */
6 | export function toAddress(addressValue: AddressType): Address {
7 | if (addressValue instanceof Address) {
8 | return addressValue;
9 | }
10 |
11 | return address(addressValue.toString());
12 | }
13 |
--------------------------------------------------------------------------------
/packages/sdk/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@ston-fi/typescript-config/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/sdk/tsup.config.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore - esbuild-analyzer package is not typed
2 | import AnalyzerPlugin from "esbuild-analyzer";
3 | import { defineConfig } from "tsup";
4 |
5 | export default defineConfig({
6 | entryPoints: ["src/", "!src/test-utils", "!src/**/*.test.ts"],
7 | format: ["esm", "cjs"],
8 | outDir: "dist",
9 | dts: true,
10 | clean: true,
11 | sourcemap: true,
12 | splitting: true,
13 | esbuildPlugins: [
14 | AnalyzerPlugin({
15 | outfile: "./build-report.html",
16 | }),
17 | ],
18 | });
19 |
--------------------------------------------------------------------------------
/packages/typescript-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ston-fi/typescript-config",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "publishConfig": {
6 | "access": "public"
7 | },
8 | "devDependencies": {
9 | "@total-typescript/tsconfig": "^1.0.4"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/typescript-config/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "extends": "@total-typescript/tsconfig/bundler/no-dom/library-monorepo"
5 | }
6 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'examples/*'
3 | - 'packages/*'
4 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "tasks": {
4 | "dev": {
5 | "dependsOn": ["^build"]
6 | },
7 | "build": {
8 | "dependsOn": ["^build"],
9 | "outputs": ["dist/**"]
10 | },
11 | "test": {
12 | "dependsOn": ["^build"],
13 | "outputs": ["coverage/**"]
14 | },
15 | "lint": {}
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
| | | | | | |