├── .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 |
28 | 29 | 30 |
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 | 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 | 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 | 75 | ); 76 | } 77 | 78 | if (!offerAsset || !askAsset) { 79 | return ( 80 | 83 | ); 84 | } 85 | 86 | if (!offerAmount && !askAmount) { 87 | return ( 88 | 91 | ); 92 | } 93 | 94 | if (swapSimulationQuery.isLoading) { 95 | return ( 96 | 99 | ); 100 | } 101 | 102 | if (!swapSimulationQuery.data) { 103 | return ; 104 | } 105 | 106 | return ( 107 | 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 | 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 | 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 |
53 |
54 | 55 | 60 | setSlippageTolerance( 61 | transformValueBack(Number.parseFloat(e.target.value)), 62 | ) 63 | } 64 | /> 65 |
66 | {SLIPPAGE_TOLERANCE_OPTIONS.map((value) => ( 67 | 74 | ))} 75 |
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 |
    56 | 72 |
  • 73 | Offer amount: 74 | 75 | {bigNumberToFloat(data.offerUnits, offerAsset.meta?.decimals ?? 9)} 76 |   77 | {offerAsset.meta?.symbol} 78 | 79 |
  • 80 |
  • 81 | Ask amount: 82 | 83 | {bigNumberToFloat(data.askUnits, askAsset.meta?.decimals ?? 9)} 84 |   85 | {askAsset.meta?.symbol} 86 | 87 |
  • 88 |
  • 89 | Ask amount (min): 90 | 91 | {bigNumberToFloat(data.minAskUnits, askAsset.meta?.decimals ?? 9)} 92 |   93 | {askAsset.meta?.symbol} 94 | 95 |
  • 96 |
  • 97 | Price impact: 98 | 99 | {(Number(data.priceImpact) * 100).toFixed(2)}% 100 | 101 |
  • 102 |
  • 103 | Slippage tolerance (max): 104 | 105 | {(Number(data.slippageTolerance) * 100 * 100).toFixed(2)}% 106 | 107 |
  • 108 |
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 | 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 |
14 |
15 | 20 | logo 21 | 22 | example 23 | 24 | 25 | 26 | 27 | 32 | GitHub 33 | 34 | 39 | GitBook 40 | 41 |
42 |
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 | 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 | [![TON](https://img.shields.io/badge/based%20on-TON-blue)](https://ton.org/) 6 | [![License](https://img.shields.io/npm/l/@ston-fi/sdk)](https://img.shields.io/npm/l/@ston-fi/sdk) 7 | [![npm version](https://img.shields.io/npm/v/@ston-fi/sdk/latest.svg)](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 | --------------------------------------------------------------------------------