Repository: near/near-sdk-js Branch: develop Commit: 8431739ff5e4 Files: 348 Total size: 997.0 KB Directory structure: gitextract_uwvzvvaa/ ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── add-to-devtools.yml │ ├── tests.yml │ ├── typedoc-generate-gitbook-docs.yml │ └── typedoc-generator.yml ├── .gitignore ├── .npmrc ├── AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── LICENSE-APACHE ├── README.md ├── RELEASE.md ├── RUNTIME_INVESTIGATE.md ├── SECURITY.md ├── TOOLING.md ├── benchmark/ │ ├── .gitignore │ ├── README.md │ ├── __tests__/ │ │ ├── test-collections-performance.ava.js │ │ ├── test-deploy-contract.ava.js │ │ ├── test-expensive-calc.ava.js │ │ ├── test-highlevel-collection.ava.js │ │ ├── test-highlevel-minimal.ava.js │ │ ├── test-lowlevel-api.ava.js │ │ ├── test-lowlevel-minimal.ava.js │ │ └── util.js │ ├── ava.config.cjs │ ├── example-outcome.json │ ├── jsconfig.json │ ├── package.json │ ├── res/ │ │ ├── deploy_contract.wasm │ │ ├── expensive_calc.wasm │ │ ├── highlevel_collection.wasm │ │ ├── highlevel_minimal.wasm │ │ ├── lowlevel_api.wasm │ │ └── lowlevel_minimal.wasm │ ├── src/ │ │ ├── deploy-contract.js │ │ ├── expensive-calc.js │ │ ├── highlevel-collection.js │ │ ├── highlevel-minimal.js │ │ ├── lookup-map.js │ │ ├── lookup-set.js │ │ ├── lowlevel-api.js │ │ ├── lowlevel-minimal.js │ │ ├── unordered-map.js │ │ ├── unordered-set.js │ │ └── vector.js │ └── tsconfig.json ├── examples/ │ ├── .gitignore │ ├── README.md │ ├── __tests__/ │ │ ├── standard-ft/ │ │ │ └── ft-tests.ava.js │ │ ├── standard-nft/ │ │ │ ├── test_approval.ava.js │ │ │ ├── test_core.ava.js │ │ │ └── test_enumeration.ava.js │ │ ├── test-basic-updates.ava.js │ │ ├── test-clean-state.ava.js │ │ ├── test-counter.ava.js │ │ ├── test-cross-contract-call-loop.ava.js │ │ ├── test-cross-contract-call-ts.ava.js │ │ ├── test-cross-contract-call.ava.js │ │ ├── test-fungible-token-lockable.ava.js │ │ ├── test-fungible-token.ava.js │ │ ├── test-nested-collections.ava.js │ │ ├── test-non-fungible-token.ava.js │ │ ├── test-parking-lot.ava.js │ │ ├── test-programmatic-update.ava.js │ │ ├── test-state-message-migration-add-filed.ava.js │ │ ├── test-state-migration.ava.js │ │ ├── test-status-deserialize-class.ava.js │ │ ├── test-status-message-borsh.ava.js │ │ ├── test-status-message-collections.ava.js │ │ ├── test-status-message-deserialize-err.ava.js │ │ ├── test-status-message-serialize-err.ava.js │ │ └── test-status-message.ava.js │ ├── ava.config.cjs │ ├── jsconfig.json │ ├── package.json │ ├── res/ │ │ └── defi.wasm │ ├── src/ │ │ ├── basic-updates/ │ │ │ ├── basic-updates-base.js │ │ │ └── basic-updates-update.js │ │ ├── clean-state.js │ │ ├── counter/ │ │ │ ├── counter-lowlevel.js │ │ │ ├── counter.js │ │ │ ├── counter.ts │ │ │ └── log.ts │ │ ├── cross-contract-calls/ │ │ │ ├── cross-contract-call-loop.js │ │ │ ├── cross-contract-call.js │ │ │ └── cross-contract-call.ts │ │ ├── fungible-token/ │ │ │ ├── fungible-token-helper.ts │ │ │ ├── fungible-token-lockable.js │ │ │ ├── fungible-token.ts │ │ │ └── my-ft.ts │ │ ├── nested-collections.ts │ │ ├── non-fungible-token/ │ │ │ ├── my-nft.ts │ │ │ ├── non-fungible-token-receiver.js │ │ │ ├── non-fungible-token.js │ │ │ ├── test-approval-receiver.ts │ │ │ └── test-token-receiver.ts │ │ ├── parking-lot.ts │ │ ├── programmatic-updates/ │ │ │ ├── programmatic-update-after.ts │ │ │ └── programmatic-update-before.ts │ │ ├── state-migration/ │ │ │ ├── state-migration-new.ts │ │ │ └── state-migration-original.ts │ │ ├── status-deserialize-class.js │ │ └── status-message/ │ │ ├── status-message-borsh.js │ │ ├── status-message-collections.js │ │ ├── status-message-deserialize-err.js │ │ ├── status-message-migrate-add-field.js │ │ ├── status-message-serialize-err.js │ │ └── status-message.js │ └── tsconfig.json ├── generate-docs-markdown.js ├── near-sdk-js@2.0.0-diff-1.0.0.md ├── package.json ├── packages/ │ ├── near-contract-standards/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── ava.config.cjs │ │ ├── lib/ │ │ │ ├── event.d.ts │ │ │ ├── event.js │ │ │ ├── fungible_token/ │ │ │ │ ├── core.d.ts │ │ │ │ ├── core.js │ │ │ │ ├── core_impl.d.ts │ │ │ │ ├── core_impl.js │ │ │ │ ├── events.d.ts │ │ │ │ ├── events.js │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.js │ │ │ │ ├── metadata.d.ts │ │ │ │ ├── metadata.js │ │ │ │ ├── receiver.d.ts │ │ │ │ ├── receiver.js │ │ │ │ ├── resolver.d.ts │ │ │ │ └── resolver.js │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── non_fungible_token/ │ │ │ │ ├── approval/ │ │ │ │ │ ├── approval_receiver.d.ts │ │ │ │ │ ├── approval_receiver.js │ │ │ │ │ ├── index.d.ts │ │ │ │ │ └── index.js │ │ │ │ ├── core/ │ │ │ │ │ ├── index.d.ts │ │ │ │ │ ├── index.js │ │ │ │ │ ├── receiver.d.ts │ │ │ │ │ ├── receiver.js │ │ │ │ │ ├── resolver.d.ts │ │ │ │ │ └── resolver.js │ │ │ │ ├── enumeration/ │ │ │ │ │ ├── index.d.ts │ │ │ │ │ └── index.js │ │ │ │ ├── events.d.ts │ │ │ │ ├── events.js │ │ │ │ ├── impl.d.ts │ │ │ │ ├── impl.js │ │ │ │ ├── index.d.ts │ │ │ │ ├── index.js │ │ │ │ ├── metadata.d.ts │ │ │ │ ├── metadata.js │ │ │ │ ├── token.d.ts │ │ │ │ ├── token.js │ │ │ │ ├── utils.d.ts │ │ │ │ └── utils.js │ │ │ ├── storage_management/ │ │ │ │ ├── index.d.ts │ │ │ │ └── index.js │ │ │ ├── util.d.ts │ │ │ └── util.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── event.ts │ │ │ ├── fungible_token/ │ │ │ │ ├── core.ts │ │ │ │ ├── core_impl.ts │ │ │ │ ├── events.ts │ │ │ │ ├── index.ts │ │ │ │ ├── metadata.ts │ │ │ │ ├── receiver.ts │ │ │ │ └── resolver.ts │ │ │ ├── index.ts │ │ │ ├── non_fungible_token/ │ │ │ │ ├── approval/ │ │ │ │ │ ├── approval_receiver.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── core/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── receiver.ts │ │ │ │ │ └── resolver.ts │ │ │ │ ├── enumeration/ │ │ │ │ │ └── index.ts │ │ │ │ ├── events.ts │ │ │ │ ├── impl.ts │ │ │ │ ├── index.ts │ │ │ │ ├── metadata.ts │ │ │ │ ├── token.ts │ │ │ │ └── utils.ts │ │ │ ├── storage_management/ │ │ │ │ └── index.ts │ │ │ └── util.ts │ │ └── tsconfig.json │ └── near-sdk-js/ │ ├── .eslintrc.cjs │ ├── .prettierignore │ ├── README.md │ ├── builder/ │ │ └── builder.c │ ├── lib/ │ │ ├── api.d.ts │ │ ├── api.js │ │ ├── cli/ │ │ │ ├── abi.d.ts │ │ │ ├── abi.js │ │ │ ├── build-tools/ │ │ │ │ ├── include-bytes.d.ts │ │ │ │ ├── include-bytes.js │ │ │ │ ├── near-bindgen-exporter.d.ts │ │ │ │ └── near-bindgen-exporter.js │ │ │ ├── cli.d.ts │ │ │ ├── cli.js │ │ │ ├── post-install.d.ts │ │ │ ├── post-install.js │ │ │ ├── utils.d.ts │ │ │ └── utils.js │ │ ├── collections/ │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── lookup-map.d.ts │ │ │ ├── lookup-map.js │ │ │ ├── lookup-set.d.ts │ │ │ ├── lookup-set.js │ │ │ ├── subtype.d.ts │ │ │ ├── subtype.js │ │ │ ├── unordered-map.d.ts │ │ │ ├── unordered-map.js │ │ │ ├── unordered-set.d.ts │ │ │ ├── unordered-set.js │ │ │ ├── vector.d.ts │ │ │ └── vector.js │ │ ├── index.d.ts │ │ ├── index.js │ │ ├── near-bindgen.d.ts │ │ ├── near-bindgen.js │ │ ├── promise.d.ts │ │ ├── promise.js │ │ ├── types/ │ │ │ ├── account_id.d.ts │ │ │ ├── account_id.js │ │ │ ├── collections.d.ts │ │ │ ├── collections.js │ │ │ ├── gas.d.ts │ │ │ ├── gas.js │ │ │ ├── index.d.ts │ │ │ ├── index.js │ │ │ ├── primitives.d.ts │ │ │ ├── primitives.js │ │ │ ├── public_key.d.ts │ │ │ ├── public_key.js │ │ │ ├── vm_types.d.ts │ │ │ └── vm_types.js │ │ ├── utils.d.ts │ │ ├── utils.js │ │ ├── version.d.ts │ │ └── version.js │ ├── package.json │ ├── src/ │ │ ├── api.ts │ │ ├── cli/ │ │ │ ├── abi.ts │ │ │ ├── build-tools/ │ │ │ │ ├── include-bytes.ts │ │ │ │ └── near-bindgen-exporter.ts │ │ │ ├── cli.ts │ │ │ ├── post-install.ts │ │ │ └── utils.ts │ │ ├── collections/ │ │ │ ├── index.ts │ │ │ ├── lookup-map.ts │ │ │ ├── lookup-set.ts │ │ │ ├── subtype.ts │ │ │ ├── unordered-map.ts │ │ │ ├── unordered-set.ts │ │ │ └── vector.ts │ │ ├── index.ts │ │ ├── near-bindgen.ts │ │ ├── promise.ts │ │ ├── types/ │ │ │ ├── account_id.ts │ │ │ ├── collections.ts │ │ │ ├── gas.ts │ │ │ ├── index.ts │ │ │ ├── primitives.ts │ │ │ ├── public_key.ts │ │ │ └── vm_types.ts │ │ ├── utils.ts │ │ └── version.ts │ ├── tsconfig.json │ └── typedoc.json ├── pnpm-workspace.yaml ├── tests/ │ ├── .gitignore │ ├── README.md │ ├── __tests__/ │ │ ├── abi/ │ │ │ ├── abi.ava.js │ │ │ ├── testcases/ │ │ │ │ ├── json_schema.ts │ │ │ │ ├── modifiers.ts │ │ │ │ ├── return.ts │ │ │ │ └── simple_function.ts │ │ │ └── util.js │ │ ├── bytes.ava.js │ │ ├── constructor_validation.ava.js │ │ ├── decorators/ │ │ │ ├── migrate.ava.js │ │ │ ├── near_bindgen.ava.js │ │ │ ├── payable.ava.js │ │ │ └── private.ava.js │ │ ├── function-params.ava.js │ │ ├── lookup-map.ava.js │ │ ├── lookup-set.ava.js │ │ ├── test-bigint-serialization.ava.js │ │ ├── test-date-serialization.ava.js │ │ ├── test-middlewares.ava.js │ │ ├── test-public-key.ava.js │ │ ├── test_alt_bn128_api.ava.js │ │ ├── test_context_api.ava.js │ │ ├── test_highlevel_promise.ava.js │ │ ├── test_log_panic_api.ava.js │ │ ├── test_math_api.ava.js │ │ ├── test_promise_api.ava.js │ │ ├── test_storage_api.ava.js │ │ ├── typescript.ava.js │ │ ├── unordered-map.ava.js │ │ ├── unordered-set.ava.js │ │ └── vector.ava.js │ ├── ava.config.cjs │ ├── jsconfig.json │ ├── package.json │ ├── src/ │ │ ├── alt_bn128_api.js │ │ ├── bigint-serialization.ts │ │ ├── bytes.js │ │ ├── constructor-validation/ │ │ │ ├── 1-parameter-not-set-in-constructor.ts │ │ │ ├── all-parameters-set-in-constructor.ts │ │ │ ├── no-constructor.ts │ │ │ └── no-parameters-set-in-constructor.ts │ │ ├── context_api.js │ │ ├── date-serialization.ts │ │ ├── decorators/ │ │ │ ├── payable.ts │ │ │ ├── private.ts │ │ │ ├── require_init_false.ts │ │ │ └── require_init_true.ts │ │ ├── function-params.js │ │ ├── highlevel-promise.js │ │ ├── log_panic_api.js │ │ ├── lookup-map.js │ │ ├── lookup-set.js │ │ ├── math_api.js │ │ ├── middlewares.ts │ │ ├── migrate.ts │ │ ├── model.js │ │ ├── promise_api.js │ │ ├── promise_batch_api.js │ │ ├── public-key.js │ │ ├── storage_api.js │ │ ├── typescript.ts │ │ ├── unordered-map.js │ │ ├── unordered-set.js │ │ └── vector.js │ └── tsconfig.json ├── turbo.json └── typedoc.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ pnpm-lock.yaml linguist-generated=true -diff /**/lib/**/*.js linguist-generated=true -diff /**/lib/**/*.d.ts linguist-generated=true -diff near-contract-standards/lib/**/*.js linguist-generated=true -diff near-contract-standards/lib/**/*.d.ts linguist-generated=true -diff ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: Bug Report description: Submit a bug report to help us improve labels: [bug, 'status: needs triage'] body: - type: markdown attributes: value: | Before filing your issue, ask yourself: - Is this clearly a NEAR JavaScript SDK defect? - Do I have basic ideas about where it goes wrong? (For example, if there are stack traces, are they pointing to one file?) **Please don't use issues for just questions**. For support please use [GitHub discussions](https://github.com/near/near-sdk-js/discussions). The quickest way to verify whether it's a NEAR JavaScript SDK defect is through a **reproduction**, starting with a fresh project and making changes until the bug is reproduced. - type: checkboxes attributes: label: Prerequisites description: Please check the following items before creating a issue, these are basic sanity checks to make sure that the problem isn't something too obvious. options: - label: I'm using the latest version of `near-sdk-js`. required: true - label: I have tried to start with a fresh project and reproduce the defect with minimal code changes. - label: I have read the console error messages carefully (if applicable). - type: textarea attributes: label: Description description: A clear and concise description of what the bug is. validations: required: true - type: input attributes: label: Reproducible demo description: | (Optional) Paste the link to an example repo. - type: textarea attributes: label: Steps to reproduce description: Write down the steps to reproduce the bug. placeholder: | 1. Step 1... 2. Step 2... 3. Step 3... validations: required: true - type: textarea attributes: label: Expected behavior description: | How did you expect your project to behave? If you’re unsure, write down what you thought would happen. placeholder: Write what you thought would happen. validations: required: true - type: textarea attributes: label: Actual behavior description: | Did something go wrong? Is something broken, or not behaving as you expected? Describe this section in detail. Please submit complete log messages with the stack traces. placeholder: Write what happened with any error messages. validations: required: true - type: textarea attributes: label: Your environment description: Include as many relevant details about the environment you experienced the bug in. value: | - NEAR JavaScript SDK version used: - Relevant dependencies (if applicable): - type: checkboxes attributes: label: Self-service description: | If you feel like you could contribute to this issue, please check the box below. If you do check this box, please send a pull request within 7 days so we can still delegate this to someone else. options: - label: I'd be willing to fix this bug myself. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 🚀 Feature request & ❓ Questions url: https://github.com/near/near-sdk-js/discussions about: Use GitHub discussions for feature requests and questions. ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: Feature design / RFC description: Submit a detailed feature request with a concrete proposal, including an exhaustive API design labels: [ feature, 'status: needs triage' ] body: - type: markdown attributes: value: | Important things: - This is for feature request including a real API design, not just a basic idea. - The design does not have to be perfect, we'll discuss it if needed. - For a more "casual" feature request, consider using GitHub Discussions instead: https://github.com/near/near-sdk-js/discussions/categories/ideas - type: checkboxes attributes: label: Have you read the Contributing Guidelines? options: - label: I have read the [Contributing Guidelines on issues](https://github.com/near/near-sdk-js/blob/master/CONTRIBUTING.md). required: true - type: textarea attributes: label: Description description: A clear and concise description of what the feature is. validations: required: true - type: input attributes: label: Has this been requested on GitHub Discussions? description: Please post the [GitHub Discussions](https://github.com/near/near-sdk-js/discussions/categories/ideas) link, it is helpful to see how much interest there is for this feature. - type: textarea attributes: label: Motivation description: Please outline the motivation for the proposal and why it should be implemented. validations: required: true - type: textarea attributes: label: API design description: | Please describe how users will use this feature. Please explain in an exhaustive way what are the config options and their respective effects. > **What happens if you skip this step?** This issue may be closed without any in-depth discussion. Your feature request is just an idea for now, please use GitHub Discussions for that: https://github.com/near/near-sdk-js/discussions/categories/ideas - type: checkboxes attributes: label: Self-service description: | If you feel like you could contribute to this issue, please check the box below. If you do check this box, please send a pull request within 7 days so we can still delegate this to someone else. Note that for feature issues, we still require you to fully fill out this form and reach consensus with the maintainers on API design before rushing to implement it, so that you don't waste your time. options: - label: I'd be willing to contribute and develop this feature myself. ================================================ FILE: .github/pull_request_template.md ================================================ ## Pre-flight checklist - [ ] I have read the [Contributing Guidelines on pull requests](https://github.com/near/near-sdk-js/blob/master/CONTRIBUTING.md). - [ ] Commit messages follow the [conventional commits](https://www.conventionalcommits.org/) spec - [ ] **If this is a code change**: I have written unit tests. - [ ] **If this is a new API or substantial change**: the PR has an accompanying issue (closes #0000) and the maintainers have approved on my working plan. ## Motivation ## Test Plan ## Related issues/PRs ================================================ FILE: .github/workflows/add-to-devtools.yml ================================================ name: 'Add to DevTools Project' on: issues: types: - opened - reopened pull_request_target: types: - opened - reopened jobs: add-to-project: name: Add issue/PR to project runs-on: ubuntu-latest steps: - uses: actions/add-to-project@v1.0.0 with: # add to DevTools Project #156 project-url: https://github.com/orgs/near/projects/156 github-token: ${{ secrets.PROJECT_GH_TOKEN }} ================================================ FILE: .github/workflows/tests.yml ================================================ name: Test on: pull_request: push: branches: - master - develop jobs: tests: strategy: matrix: platform: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v3 - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 9 - name: Setup Node uses: actions/setup-node@v3 with: node-version: 18.x cache: pnpm - name: Install dependencies run: pnpm install - name: Lint code run: pnpm lint - name: Format code run: pnpm format - name: Run git status run: git status - name: Check that lints where commited run: test -z "$(git status --porcelain)" - name: Build run: pnpm build - name: Check that all build artifacts where commited run: test -z "$(git status --porcelain)" - name: Run tests run: export NEAR_WORKSPACES_DEBUG=true && pnpm test ================================================ FILE: .github/workflows/typedoc-generate-gitbook-docs.yml ================================================ name: Deploy TypeDoc to GitBook on: push: branches: - develop env: NODE_VERSION: 18.x ENTRY_FILE: "packages" CONFIG_PATH: "tsconfig.base.json" USES_PNPM: "true" jobs: deploy: concurrency: ci-${{ github.ref }} runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[skip ci]')" permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 9 - name: Install dependencies run: pnpm install --no-frozen-lockfile - name: Build project run: pnpm build - name: Build documentation run: pnpm docs:generate-markdown - name: Deploy to GitBook run: | git clone --branch=git-book https://frol:${{ secrets.MY_GITHUB_TOKEN }}@github.com/near/near-sdk-js.git book rsync -av --delete --exclude=.git markdown-docs/ book/ cd book git config user.name "GitHub Actions" git config user.email "actions@github.com" git add . git commit -m "Update documentation [skip ci]" git push https://frol:${{ secrets.MY_GITHUB_TOKEN }}@github.com/near/near-sdk-js.git git-book ================================================ FILE: .github/workflows/typedoc-generator.yml ================================================ name: Deploy TypeDoc on GitHub pages on: push: branches: develop env: NODE_VERSION: 18.x ENTRY_FILE: 'packages' CONFIG_PATH: 'tsconfig.base.json' USES_PNPM: 'true' jobs: deploy: concurrency: ci-${{ github.ref }} runs-on: ubuntu-latest permissions: contents: write steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: ${{ env.NODE_VERSION }} - name: Setup pnpm uses: pnpm/action-setup@v4 with: version: 7 - name: Install dependencies run: pnpm install --no-frozen-lockfile - name: Build project run: pnpm build - name: Build documentation run: pnpm docs:generate - name: Deploy to GitHub pages uses: JamesIves/github-pages-deploy-action@v4 with: token: ${{ secrets.GITHUB_TOKEN }} branch: gh-pages folder: docs clean: true ================================================ FILE: .gitignore ================================================ node_modules vendor .idea deps .turbo yarn.lock package-lock.json ================================================ FILE: .npmrc ================================================ save-exact=true ================================================ FILE: AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md ================================================ # JSON Schemas for Automatic Decoding of the State A limitation that we early detected in the `near-sdk-js` is that Classes and Nested Structures (e.g. Vectors of Maps) are valid to declare as attributes of a contract, but hard to correctly deserialize. This doc explains a new solution currently implemented in the SDK and how to use it to simplify hanlding stored Classes and Nested Structures. ## The Problem NEAR smart contracts store information in their state, which they read when an execution starts and write when an execution finished. In particular, all the information stored in the contract is (de)serialized as a `utf8` `JSON-String`. Since Javascript does **not** handle types, it is actually very hard to infer the type of the data that is stored when the contract is loaded at the start of an execution. Imagine for example a contract storing a class `Car` defined as follows: ```typescript Class Car { name: string; speed: number; run() { // ... } } ``` A particular instance of that Car (e.g. new Car("Audi", 200)) will be stored in the contract as the JSON string: ```json {"name": "Audi", "speed": 200} ``` Next time the contract is called, the state will be parsed using `JSON.parse()`, and the result will be an `Object {name: "Audi", speed:200}`, which is an instance of `object` and **not an instance of Car**. This would happen both if the user wrote the contract in `javascript` or `typescript`, since casting in `Typescript` is just sugarcoating, it does not actually cast the object! What this means is that: ```js // the SDK parses the String into an Object this.car.run() # This will fail! ``` This problem is particularly painful when the class is nested in another Class, e.g. a `LookupMap` of `Cars`. ## The (non-elegant) Solution Before, the SDK mitigated this problem by requiring the user to manually reconstruct the JS `Object` to an instance of the original class. ## A More Elegant Solution: JSON Schemas To help the SDK know which type it should decode, we can add a `static schema` map, which tells the SDK what type of data it should read: ```ts Class Car { // Schema to (de)serialize static schema = { name: "string", speed: "number", }; // Properties name: string; speed: number; // methods run() { // ... } } ``` If a `Class` defines an schema, the SDK will recursively reconstruct it, by creating a new instance of `Car` and filling its attributes with the right values. In this way, the deserialized object will effectively be **an instance of the Class**. This means that we can call all its methods: ```js // the SDK iteratively reconstructs the Car this.car.run() # This now works! ``` ## The schema format The Schema supports multiple types: * Primitive types: `string`, `number`, `boolean`. We can remove schema format of `Primitive types` since is no need to reconstruct them. * Built-in object types: `Date`, `BigInt`. * Built-in collections: `array`, `map` * Arrays need to be declared as `{array: {value: valueType}}`, there are no reconstruct for `Primitive types`, for the value type is `Primitive types`, we can remove this field. * Maps need to declared as `{map: {key: 'keyType', value: 'valueType'}}`, there are no reconstruct for `Primitive types`, for the key and value type are `Primitive types`, we can remove this field. * Custom classes are denoted by their name, e.g. `Car` * Near SDK Collections (i.e. `Vector`, `LookupMap`, `LookupSet`, `UnorderedMap`, `UnorderedSet`) need to be declared as `{class: ClassType, value: ValueType}` if we need to reconstruct value or we can simplify to mark `ClassType` if we no need to reconstruct value for `Primitive types`. You can see a complete example in the [status-deserialize-class](./examples/src/status-deserialize-class.js) file, which contains the following Class declaration: ```js export class StatusDeserializeClass { static schema = { truck: Truck, efficient_recordes: UnorderedMap, nested_efficient_recordes: {class: UnorderedMap, value: UnorderedMap}, nested_lookup_recordes: {class: UnorderedMap, value: LookupMap}, vector_nested_group: {class: Vector, value: LookupMap}, lookup_nest_vec: { class: LookupMap, value: Vector }, unordered_set: UnorderedSet, user_car_map: {class: UnorderedMap, value: Car }, big_num: 'bigint', date: 'date' }; constructor() { this.is_inited = false; this.records = {}; this.truck = new Truck(); this.messages = []; this.efficient_recordes = new UnorderedMap("a"); this.nested_efficient_recordes = new UnorderedMap("b"); this.nested_lookup_recordes = new UnorderedMap("c"); this.vector_nested_group = new Vector("d"); this.lookup_nest_vec = new LookupMap("e"); this.unordered_set = new UnorderedSet("f"); this.user_car_map = new UnorderedMap("g"); this.big_num = 1n; this.date = new Date(); this.message_without_schema_defined = ""; this.number_without_schema_defined = 0; this.records_without_schema_defined = {}; } // other methods } ``` --- #### What happens with the old `reconstructor`? Until now, users needed to call a `reconstructor` method in order for **Nested Collections** to be properly decoded: ```typescript @NearBindgen({}) export class Contract { outerMap: UnorderedMap>; constructor() { this.outerMap = new UnorderedMap("o"); } @view({}) get({id, accountId}: { id: string; accountId: string }) { const innerMap = this.outerMap.get(id, { reconstructor: UnorderedMap.reconstruct, // we need to announce reconstructor explicit }); if (innerMap === null) { return null; } return innerMap.get(accountId); } } ``` With schemas, this is no longer needed, as the SDK can correctly infer how to decode the Nested Collections: ```typescript @NearBindgen({}) export class Contract { static schema = { outerMap: {class: UnorderedMap, value: UnorderedMap} }; outerMap: UnorderedMap>; constructor() { this.outerMap = new UnorderedMap("o"); } @view({}) get({id, accountId}: { id: string; accountId: string }) { const innerMap = this.outerMap.get(id); // reconstructor can be infered from static schema if (innerMap === null) { return null; } return innerMap.get(accountId); } } ``` --- #### How Does the Reconstruction Work? The `_reconstruct` method in [near-bindgen.ts](./packages/near-sdk-js/src/near-bindgen.ts) will check whether an schema exists in the **contract's class**. If such schema exists, it will try to decode it by invoking `decodeObj2class`: ```typescript static _reconstruct(classObject: object, plainObject: AnyObject): object { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (classObject.constructor.schema === undefined) { for (const item in classObject) { const reconstructor = classObject[item].constructor?.reconstruct; classObject[item] = reconstructor ? reconstructor(plainObject[item]) : plainObject[item]; } return classObject; } return decodeObj2class(classObject, plainObject); } ``` ================================================ FILE: CODEOWNERS ================================================ * @ailisp @volovyks ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at social@nearprotocol.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Near JavaScript SDK First off, thanks for taking the time to contribute! We look forward to to your contributions. 🎉 All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. We take community contributions very seriously, as we understand you invest precious time and effort. That is why we have this guide to set mutual expectations, to avoid unfortunate situation where we can't accept a contribution because it may not fit project's goals, or lacks needed quality standards. There is one important rule to follow - please let us know ahead about what you plan to do, and get an OK from our team so that we can prepare for accepting your contribution. > And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about: > - Star the project > - Tweet about it > - Refer this project in your project's readme > - Mention the project at local meetups and tell your friends/colleagues ## Table of Contents - [I Have a Question](#i-have-a-question) - [Get Involved](#get-involved) - [Our Development Process](#our-development-process) - [Development](#development) - [Pull Requests](#pull-requests) - [Tests](#tests) - [What Happens Next?](#what-happens-next) ## I Have a Question If you want to ask a question please use the following resources available for you: - [Docs site](https://docs.near.org/sdk/js/introduction) - Post a question on [GitHub Discussions](https://github.com/near/near-sdk-js/discussions) - Post a question on [StackOverflow with `[nearprotocol]` tag](https://stackoverflow.com/questions/tagged/nearprotocol) ## Get Involved There are many ways to contribute to this project, and many of them do not involve writing any code. Here's a few ideas to get started: - Simply start using the library. Go through the [user guide](https://docs.near.org/sdk/js/introduction). Does everything work as expected? If not, we're always looking for improvements. Let us know on [GitHub Discussions](https://github.com/near/near-sdk-js/discussions). - Look through the [open issues](https://github.com/near/near-sdk-js/issues). Provide workarounds or ask for clarification. - If you find an issue you would like to fix, [open a pull request](#pull-requests). Issues tagged as [_Good first issue_](https://github.com/near/near-sdk-js/labels/good_first_issue) are a good place to get started. - Take a look at the [enhancements requested](https://github.com/near/near-sdk-js/labels/enhancement) by others in the community and consider opening a pull request if you see something you want to work on. If you think you need help planning your contribution, please ping us on [Discord](https://near.chat) and let us know you are looking for a bit of help. ### Join our Discord Channel We have the [`#dev-feedback`](https://discord.gg/XKGrd9h9TB) channel on [Discord](https://near.chat) to discuss all things about NEAR development. You can also be of great help by helping other users in the [`#dev-support`](https://discord.gg/Fy4WzwRgun) channel. ### Join our Telegram User Group You can have near-sdk-js related discussions with other users, contributors and maintainers on [Telegram]( https://t.me/near_sdk_js). ### Triaging Issues and Pull Requests One great way you can contribute to the project without writing any code is to help triage issues and pull requests as they come in. You can review code, or ask for more information if you believe the issue does not provide all the details required to solve it. ## Our Development Process Our core team works directly in the public repo, and pull requests are checked by the continuous integration system, GitHub actions. There are unit tests and end-to-end tests. **Branch organization**: This project has one primary branch `develop` and we use feature branches to deliver new features with pull requests. **Issue templates**: When [opening a new issue](https://github.com/near/near-sdk-js/issues/new/choose), always **make sure to fill out the issue template**. The issue template is very important, as it sets mutual expectations about what you plan to do, and help us prepare to accept your contribution. **Bugs**: If you would like to report a problem use the [bug report](https://github.com/near/near-sdk-js/issues/new?assignees=&template=bug.yml) issue template. **Security Bugs**: To report security issues in this project please email [security@near.org](mailto:security@near.org) **New features and enhancements**: If you have a casual feature request or proposal, you can raise it in our [GitHub Discussions](https://github.com/near/near-sdk-js/discussions/categories/ideas). If you'd like to work on a PR with an implementation of your proposal, please file an issue with the [feature template](https://github.com/near/near-sdk-js/issues/new?template=feature.yml) in the form of an **elaborated RFC**. Please wait for our team to respond with an approval before you start working on it, as we can only accept PRs that are aligned with project's goals and roadmap, so we really want to avoid situations where we reject something you worked hard on. (BTW, if you think that project's goals or roadmap should change in some way, do feel free to raise it in [GitHub Discussions](https://github.com/near/near-sdk-js/discussions/) :) and we'll talk about it). ### Claiming issues We have a list of [beginner-friendly issues](https://github.com/near/near-sdk-js/labels/good_first_issue) to help you get started with the codebase and get familiar with our contribution process. This is a great place to start. Apart from the `good first issue`, the following labels are also worth looking at: - [`help wanted`](https://github.com/near/near-sdk-js/labels/help%20wanted): if you have specific knowledge in one domain, working on these issues can make your expertise shine. - [`accepting pr`](https://github.com/near/near-sdk-js/labels/status%3A%20accepting%20pr): community contributors can feel free to claim any of these. If you want to work on any of these issues, just drop a message saying "I'd like to work on this", and we will assign the issue to you and update the issue's status as "claimed". **You are expected to send a pull request within seven days** after that, so we can still delegate the issue to someone else if you are unavailable. ## Development ### Online one-click setup for contributing You can use Gitpod (a free, online, VSCode-like IDE) for contributing. With a single click, it will launch a workspace and automatically: - clone the repo. - install the dependencies. So that you can start contributing straight away. [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/near/near-sdk-js) ## Pull Requests We appreciate the time and effort you invested! 🙏 We will do our best to work with you and get the PR looked at. > Working on your first-ever Pull Request? You can learn how from this free video series: > [**How to Contribute to an Open Source Project on GitHub**](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) Please make sure the following is done when submitting a pull request: 1. **Keep your PR small.** Small pull requests (~300 lines of diff) are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise please split it. 2. **Use descriptive titles.** It is recommended to follow semantic commit conventions: https://www.conventionalcommits.org/en/v1.0.0/ 3. **Test your changes.** Include unit tests for any public API you touch. All pull requests should be opened against the `develop` branch. ### Tests Please include unit tests for any public api you add or touch. We use [AVA](https://github.com/avajs/ava) for tests. ### What Happens Next? Our team will be monitoring pull requests. Do help us by keeping pull requests consistent by following the guidelines above. ================================================ FILE: LICENSE ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: LICENSE-APACHE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # NEAR JavaScript SDK

Documentation Version License: LICENSE License: LICENSE

A JavaScript library for writing NEAR smart contracts. ```typescript import { NearBindgen, near, call, view } from 'near-sdk-js'; @NearBindgen({}) class HelloNear { greeting: string = 'Hello'; @view({}) // This method is read-only and can be called for free get_greeting(): string { return this.greeting; } @call({}) // This method changes the state, for which it cost gas set_greeting({ greeting }: { greeting: string }): void { near.log(`Saving greeting ${greeting}`); this.greeting = greeting; } } ``` See more in the [Anatomy of a Contract](https://docs.near.org/build/smart-contracts/anatomy/). ## Documentation - [Learn how to use](https://github.com/near/create-near-app/tree/master/templates/contracts/ts) - [Learn by example with AgorApp](https://agorapp.dev/catalog/all?difficulty=&chains=near) - [Learn by example with NEAR Docs](https://docs.near.org/build/smart-contracts/quickstart) - Check our [detailed examples and tutorials](https://docs.near.org/tutorials/welcome) - Find [source code examples](https://github.com/near/near-sdk-js/tree/develop/examples) with common use cases - Lookup available features in [API reference](https://near.github.io/near-sdk-js/) - 🏠 Learn more about NEAR on our [Documentation website](https://docs.near.org/) - Breaking features diff from [SDK 2.0.0 to 1.0.0](https://github.com/near/near-sdk-js/tree/develop/near-sdk-js@2.0.0-diff-1.0.0.md) ## Prerequisites - node >=14 <16.6.0 || >16.6.0 - pnpm >=7 ## Quick Start Use [`create-near-app`](https://github.com/near/create-near-app) to quickly get started writing smart contracts in JavaScript on NEAR. npx create-near-app This will scaffold a basic template for you 😎 ## Contributing If you are interested in contributing, please look at the [contributing guidelines](https://github.com/near/near-sdk-js/tree/develop/CONTRIBUTING.md). - [Report issues you encounter](https://github.com/near/near-sdk-js/issues) 🐞 - [Provide suggestions or feedback](https://github.com/near/near-sdk-js/discussions) 💡 - [Show us what you've built!](https://github.com/near/near-sdk-js/discussions/categories/show-and-tell) 💪 ## License This repository is distributed under the terms of both the [MIT license](https://github.com/near/near-sdk-js/blob/develop/LICENSE) and the [Apache License](https://github.com/near/near-sdk-js/blob/develop/LICENSE-APACHE) (Version 2.0). See [LICENSE](https://github.com/near/near-sdk-js/tree/develop/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-sdk-js/tree/develop/LICENSE-APACHE) for details. ================================================ FILE: RELEASE.md ================================================ # Release This document describes step to do a new pre-release or formal release. ## Release Requirement - A formal release must be directly bump from a tested, DevRel Team approved pre-release, with no commits other than bump the version. - Pre-release can come with arbitrary commits to fix packaging and update documentations. Several pre-release can be released before a formal release to fix issues and address feedbacks. ## Steps for pre-release 1. Create a new branch for the release. 2. Bump version in packages/near-sdk-js/package.json and packages/near-contract-standards/package.json to the version about to release. It should be `x.y.z-0`, and next pre-release `x.y.z-1`, etc. 3. Run `pnpm publish` in packages/near-sdk-js and in packages/near-contract-standards. 4. Copy examples folder in this repo to another place, drop `node_modules`, change its package.json from: ``` "near-contract-standards": "workspace:*", "near-sdk-js": "workspace:*", ``` to the version you just released, e.g. `x.y.z-1`. 5. Build and run example tests to ensure the packaging is correct. 6. If it works, go to https://github.com/near/near-sdk-js/releases/new, create a tag for the new release from the branch you created in step 1, and write the release highlights. 7. Ask the DevRel team to test the pre-release. ## Steps for formal release 1. Create a new release branch from the candidate pre-release branch 2. Bump version in packages/near-sdk-js/package.json and packages/near-contract-standards/package.json to the version about to release. It should be `x.y.z`. 3. Run `pnpm publish` in packages/near-sdk-js and in packages/near-contract-standards. 4. Go to https://github.com/near/near-sdk-js/releases/new, create a tag for the new release branch from the branch you created in step 1, and copy the highlights from latest pre-release candidate. 5. Advertise it to the community! ================================================ FILE: RUNTIME_INVESTIGATE.md ================================================ # Runtime Investigate Investigate how to compile js/ts to wasm by any tools, and the output wasm file can be executed on wasm runtime in [nearcore](https://github.com/near/nearcore) ## Table of Contents - [quickjs](#quickjs) - [hermes](#hermes) - [wasmnizer-ts](#wasmnizer-ts) ### quickjs :white_check_mark: Currently our near-sdk-js use [quickjs engine](https://bellard.org/quickjs/) to compile js->C/C++->wasm. ### hermes :x: We can't find any tools to compile [hermes engine](https://github.com/facebook/hermes) and it's api to C/C++ or wasm directly. Hermes is not support to execute wasm. ### wasmnizer-ts :x: [Wasmnizer-ts](https://github.com/intel/Wasmnizer-ts) implements the [WasmGC proposal](https://github.com/WebAssembly/gc), but wasm runtime in nearcore doesn't support it. ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Reporting a Vulnerability To report security issues in this project please send an email to [security@near.org](mailto:security@near.org) ================================================ FILE: TOOLING.md ================================================ # Tooling This document describes at a high level some the key tools used across `near-js`. ## Package Manager - [PNPM](https://pnpm.io/) PNPM is a package manager focused on fast installations while still being disk efficient. It manages dependency installations across the monorepo as well as linking interdependent packages. Unlike other package managers, PNPM creates a non-flat `node_modules` structure which ensures packages only have access to dependencies defined in their `package.json`. ## Build System - [Turborepo](https://turborepo.org/) Turborepo provides tooling for orchestrating commands across the monorepo. Similar to Yarn Workspaces, Turborepo can run tasks within each project via a single command, but rather than executing serially, Turborepo has various performance enhancing features such as task parallelization and local/remote caching. ## Automation - [GitHub Actions](https://github.com/features/actions) Github Actions is used to automate various tasks including PR checks (linting, type checks, and tests). ================================================ FILE: benchmark/.gitignore ================================================ build ================================================ FILE: benchmark/README.md ================================================ # Gas and size benchmark compare to NEAR-SDK-RS ## Summary NEAR-SDK-JS bundles a bytecode VM with the contract bytecode to a wasm file. Currently, the bytecode VM is the QuickJS runtime with interface to NEAR and the contract bytecode is compiled from JavaScript source code with QuickJS Compiler (QJSC). This results in: - Size of a minimal contract is 500K, which is also the size of the bytecode VM. - Bytecode is more compact than wasm. Complex contract in JS adds less bytes to the equivalent wasm compiled from Rust, but due to the initial 500K size, the result contract is still bigger and within same order of magnitude: several hundred KB. - For contract that bottlenecks at calling the host functions are using similar gas in JS and Rust. - For contract that does a lot of computation in JS, the JS bytecode uses significantly more gas. - For a real world contract, if it doesn't including complex logic in JavaScript, it's usually sufficient, consider the complexity of the near contract standards. - For more complex logic, We suggest to bench the most expensive contract call, including most complex path of cross contract calls, to determine whether it fits 300T Gas limit. ## Detailed gas benchmark ### A minimal contract - RS lowlevel minimal contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 2.43T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.00005T - Gas used to refund unused gas: 0.22318T - Total gas used: 5.08T - JS lowlevel minimal contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 7.07T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11132T - WASM_INSTRUCTION : 4.53T - Gas used to refund unused gas: 0.22318T - Total gas used: 9.72T In the very minimal contract the JS adds about `1.8T` gas. The major difference is loading the QuickJS VM and near-sdk-js uses 4.53T Gas. The 500K contract loading just adds 0.1T Gas. ### A highlevel minimal contract (using nearbindgen) - highlevel-minimal.ava › RS highlevel minimal contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 2.63T - BASE : 0.79G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 35.46G - READ_CACHED_TRIE_NODE : 4.56G - READ_MEMORY_BASE : 7.83G - READ_MEMORY_BYTE : 0.04G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - TOUCHING_TRIE_NODE : 32.2G - WASM_INSTRUCTION : 0.46G - WRITE_MEMORY_BASE : 2.8G - WRITE_MEMORY_BYTE : 0.04G - Gas used to refund unused gas: 223.18G - Total gas used: 5.28T - highlevel-minimal.ava › JS highlevel minimal contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 8.39T - BASE : 1.59G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 112.03G - READ_CACHED_TRIE_NODE : 6.84G - READ_MEMORY_BASE : 7.83G - READ_MEMORY_BYTE : 0.05G - READ_REGISTER_BASE : 2.52G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - STORAGE_WRITE_VALUE_BYTE : 0.06G - TOUCHING_TRIE_NODE : 48.31G - WASM_INSTRUCTION : 5.66T - WRITE_MEMORY_BASE : 5.61G - WRITE_MEMORY_BYTE : 0.05G - WRITE_REGISTER_BASE : 2.87G - WRITE_REGISTER_BYTE : 0.01G - Gas used to refund unused gas: 223.18G - Total gas used: 11.05T JS `@NearBindgen` is more expensive, the major difference is in `WASM_INSTRUCTION`, because `@NearBindgen` does some class, object manipulation work, but Rust `near_bindgen` is a compile time code generation macro. Deduct the 4.5T loading VM and near-sdk-js, it's about 1T gas overhead. ### Low level API - RS lowlevel API contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 2.53T - BASE : 0.00026T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.00008T - READ_MEMORY_BASE : 0.00522T - READ_MEMORY_BYTE : 0.00008T - STORAGE_WRITE_BASE : 0.0642T - STORAGE_WRITE_KEY_BYTE : 0.0007T - STORAGE_WRITE_VALUE_BYTE : 0.00031T - TOUCHING_TRIE_NODE : 0.0322T - WASM_INSTRUCTION : 0.00002T - Gas used to refund unused gas: 0.22318T - Total gas used: 5.18T - JS lowlevel API contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 7.8T - BASE : 0.00026T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11119T - READ_MEMORY_BASE : 0.00522T - READ_MEMORY_BYTE : 0.00008T - STORAGE_WRITE_BASE : 0.0642T - STORAGE_WRITE_EVICTED_BYTE : 0.00032T - STORAGE_WRITE_KEY_BYTE : 0.0007T - STORAGE_WRITE_VALUE_BYTE : 0.00031T - TOUCHING_TRIE_NODE : 0.09661T - WASM_INSTRUCTION : 5.09T - WRITE_REGISTER_BASE : 0.00287T - WRITE_REGISTER_BYTE : 0.00004T - Gas used to refund unused gas: 0.22318T - Total gas used: 10.45T - JS lowlevel API contract, call many - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 8.47T - BASE : 0.00265T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11119T - READ_MEMORY_BASE : 0.0522T - READ_MEMORY_BYTE : 0.00076T - STORAGE_WRITE_BASE : 0.64197T - STORAGE_WRITE_EVICTED_BYTE : 0.00289T - STORAGE_WRITE_KEY_BYTE : 0.00705T - STORAGE_WRITE_VALUE_BYTE : 0.0031T - TOUCHING_TRIE_NODE : 0.04831T - WASM_INSTRUCTION : 5.14T - WRITE_REGISTER_BASE : 0.02579T - WRITE_REGISTER_BYTE : 0.00034T - Gas used to refund unused gas: 0.22318T - Total gas used: 11.12T In this case, JS lowlevel API contract uses same gas in the storage write API part (`STORAGE_WRITE_BASE` / `STORAGE_WRITE_KEY_BYTE` / `STORAGE_WRITE_VALUE_BYTE` ). The major excessive gas is due to the overhead of initialize QuickJS VM and loading near-sdk-js. We can see this more obviously by calling storage write for 10 times ("call many tests" in above). ### Highlevel collection - RS highlevel collection contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 3.32T - BASE : 3.18G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 70.94G - READ_CACHED_TRIE_NODE : 95.76G - READ_MEMORY_BASE : 26.1G - READ_MEMORY_BYTE : 1.87G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0.03G - STORAGE_READ_BASE : 112.71G - STORAGE_READ_KEY_BYTE : 3.44G - STORAGE_READ_VALUE_BYTE : 0.19G - STORAGE_WRITE_BASE : 256.79G - STORAGE_WRITE_EVICTED_BYTE : 1.09G - STORAGE_WRITE_KEY_BYTE : 9.23G - STORAGE_WRITE_VALUE_BYTE : 7.75G - TOUCHING_TRIE_NODE : 257.63G - WASM_INSTRUCTION : 16.36G - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.74G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 1.1G - Gas used to refund unused gas: 223.18G - Total gas used: 5.97T - JS highlevel collection contract - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 10.06T - BASE : 2.91G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 113.46G - READ_CACHED_TRIE_NODE : 72.96G - READ_MEMORY_BASE : 20.88G - READ_MEMORY_BYTE : 2G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0.03G - STORAGE_READ_BASE : 112.71G - STORAGE_READ_KEY_BYTE : 3.31G - STORAGE_READ_VALUE_BYTE : 0.53G - STORAGE_WRITE_BASE : 192.59G - STORAGE_WRITE_EVICTED_BYTE : 3.02G - STORAGE_WRITE_KEY_BYTE : 7.96G - STORAGE_WRITE_VALUE_BYTE : 9.49G - TOUCHING_TRIE_NODE : 209.33G - WASM_INSTRUCTION : 6.86T - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.9G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 1.55G - Gas used to refund unused gas: 223.18G - Total gas used: 12.71T JS SDK's collection has about 1T overhead, deduct the 4.5T VM/near-sdk-js loading and 1T `@NearBindgen`. Note this benches the most complicated `UnorderedMap`, which gas usage is strictly greater than the other collections. And the gas used in actual writing the collection to storage is similar (`STORAGE_WRITE_BASE` / `STORAGE_WRITE_KEY_BYTE` / `STORAGE_WRITE_VALUE_BYTE` ). ### Computational expensive contract - JS expensive contract, iterate 20000 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 123.26T - BASE : 1.85G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 112.09G - READ_CACHED_TRIE_NODE : 4.56G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.07G - READ_REGISTER_BASE : 2.52G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - STORAGE_WRITE_VALUE_BYTE : 0.06G - TOUCHING_TRIE_NODE : 32.2G - WASM_INSTRUCTION : 120.54T - WRITE_MEMORY_BASE : 5.61G - WRITE_MEMORY_BYTE : 0.07G - WRITE_REGISTER_BASE : 2.87G - WRITE_REGISTER_BYTE : 0.04G - Gas used to refund unused gas: 223.18G - Total gas used: 125.91T - RS expensive contract. iterate 20000 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 3.01T - BASE : 1.85G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 67.77G - READ_CACHED_TRIE_NODE : 6.84G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.06G - READ_REGISTER_BASE : 2.52G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - TOUCHING_TRIE_NODE : 48.31G - WASM_INSTRUCTION : 315.17G - WRITE_MEMORY_BASE : 5.61G - WRITE_MEMORY_BYTE : 0.07G - WRITE_REGISTER_BASE : 2.87G - WRITE_REGISTER_BYTE : 0.04G - Gas used to refund unused gas: 223.18G - Total gas used: 5.66T - RS expensive contract. iterate 10000 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 2.9T - BASE : 2.38G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 67.77G - READ_CACHED_TRIE_NODE : 13.68G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.06G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - TOUCHING_TRIE_NODE : 80.51G - WASM_INSTRUCTION : 158.89G - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.07G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 0.04G - Gas used to refund unused gas: 223.18G - Total gas used: 5.56T - RS expensive contract. iterate 100 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 2.75T - BASE : 2.38G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 67.77G - READ_CACHED_TRIE_NODE : 13.68G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.05G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_KEY_BYTE : 0.35G - TOUCHING_TRIE_NODE : 80.51G - WASM_INSTRUCTION : 4.02G - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.07G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 0.03G - Gas used to refund unused gas: 223.18G - Total gas used: 5.4T - JS expensive contract, iterate 100 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 9.09T - BASE : 2.38G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 112.09G - READ_CACHED_TRIE_NODE : 13.68G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.06G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_READ_VALUE_BYTE : 0.01G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_EVICTED_BYTE : 0.06G - STORAGE_WRITE_KEY_BYTE : 0.35G - STORAGE_WRITE_VALUE_BYTE : 0.06G - TOUCHING_TRIE_NODE : 80.51G - WASM_INSTRUCTION : 6.3T - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.07G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 0.05G - Gas used to refund unused gas: 223.18G - Total gas used: 11.75T - JS expensive contract, iterate 10000 times - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 65.94T - BASE : 2.38G - CONTRACT_LOADING_BASE : 0.04G - CONTRACT_LOADING_BYTES : 112.09G - READ_CACHED_TRIE_NODE : 13.68G - READ_MEMORY_BASE : 10.44G - READ_MEMORY_BYTE : 0.06G - READ_REGISTER_BASE : 5.03G - READ_REGISTER_BYTE : 0G - STORAGE_READ_BASE : 56.36G - STORAGE_READ_KEY_BYTE : 0.15G - STORAGE_READ_VALUE_BYTE : 0.01G - STORAGE_WRITE_BASE : 64.2G - STORAGE_WRITE_EVICTED_BYTE : 0.06G - STORAGE_WRITE_KEY_BYTE : 0.35G - STORAGE_WRITE_VALUE_BYTE : 0.06G - TOUCHING_TRIE_NODE : 80.51G - WASM_INSTRUCTION : 63.15T - WRITE_MEMORY_BASE : 8.41G - WRITE_MEMORY_BYTE : 0.08G - WRITE_REGISTER_BASE : 8.6G - WRITE_REGISTER_BYTE : 0.06G - Gas used to refund unused gas: 223.18G - Total gas used: 68.59T In this case, JS uses much more gas. Because JS Number is object and that's a lot of overhead compare to native integer arithmetic. It's even a lot of overhead compare to native float arithmetic. Also in QuickJS there's no JIT. If your contract does a lot of calculation or complex algorithm in JavaScript, it'd be better to do a similar benchmark. ### Deploy and cross contract call - JS promise batch deploy contract and call - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 25.86T - CREATE_ACCOUNT : 0.09961T - DEPLOY_CONTRACT : 3.71T - FUNCTION_CALL : 2.32T - NEW_RECEIPT : 0.10806T - TRANSFER : 0.11512T - BASE : 0.00159T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.22386T - PROMISE_RETURN : 0.00056T - READ_MEMORY_BASE : 0.01566T - READ_MEMORY_BYTE : 1.97T - UTF8_DECODING_BASE : 0.00311T - UTF8_DECODING_BYTE : 0.00525T - WASM_INSTRUCTION : 14.86T - Gas used to execute the cross contract call: 41.9T - BASE : 0.00344T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11228T - READ_MEMORY_BASE : 0.00261T - READ_MEMORY_BYTE : 0.0005T - READ_REGISTER_BASE : 0.01007T - READ_REGISTER_BYTE : 0T - WASM_INSTRUCTION : 5.47T - WRITE_MEMORY_BASE : 0.01122T - WRITE_MEMORY_BYTE : 0.00014T - WRITE_REGISTER_BASE : 0.01146T - WRITE_REGISTER_BYTE : 0.00019T - Gas used to refund unused gas for cross contract call: 0.22318T - Gas used to refund unused gas: 0.22318T - Total gas used: 70.63T - RS promise batch deploy contract and call - Gas used to convert transaction to receipt: 2.43T - Gas used to execute the receipt (actual contract call): 10.89T - CREATE_ACCOUNT : 0.09961T - DEPLOY_CONTRACT : 3.71T - FUNCTION_CALL : 2.32T - NEW_RECEIPT : 0.10806T - TRANSFER : 0.11512T - BASE : 0.00159T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11283T - PROMISE_RETURN : 0.00056T - READ_MEMORY_BASE : 0.01566T - READ_MEMORY_BYTE : 1.97T - UTF8_DECODING_BASE : 0.00311T - UTF8_DECODING_BYTE : 0.00525T - WASM_INSTRUCTION : 0.00038T - Gas used to execute the cross contract call: 41.9T - BASE : 0.00344T - CONTRACT_LOADING_BASE : 0.00004T - CONTRACT_LOADING_BYTES : 0.11228T - READ_MEMORY_BASE : 0.00261T - READ_MEMORY_BYTE : 0.0005T - READ_REGISTER_BASE : 0.01007T - READ_REGISTER_BYTE : 0T - WASM_INSTRUCTION : 5.47T - WRITE_MEMORY_BASE : 0.01122T - WRITE_MEMORY_BYTE : 0.00014T - WRITE_REGISTER_BASE : 0.01146T - WRITE_REGISTER_BYTE : 0.00019T - Gas used to refund unused gas for cross contract call: 0.22318T - Gas used to refund unused gas: 0.22318T - Total gas used: 55.67T In this test, we use a JS contract and RS contract to both deploy a JS contract and cross contract call this newly deployed contract. We can see the gas to do the cross contract call is the same. JS SDK has a `~10T` overhead to parse a `~500K` contract in byte. This is because JS, either represent code in Uint8Array or string has some overhead while rust compiler can directly turn it into data section in wasm. In practice, a 10T overhead for a one time contract deploy is not a big deal. ## Tips to do your own benchmark If the above cases don't cover use case or you have a complex algorithm to implement in JavaScript, it's a good idea to benchmark your specific algorithm before choose near-sdk-js for your project. You don't have to implement the exact algorithm to estimate the gas usage. Instead, you can find out the most expensive execution path of the algorithm, and estimate it by using the upper bound. For example, store the biggest possible objects into the collection and iterate for most possible times. Then goes to write the benchmark and the total gas cannot be more than 300T to be a valid contract. Also, if it has cross contract call, make sure the total gas, that's a sum of all cross contract calls, is less than 300T. To add your benchmark, write a one function contract of your most expensive operation. And write a test to call this function. If it doesn't involve cross contract call or promises, creating such test is simple. You can refer to `bench/src/expensive-calc.js` and `bench/__tests__/test-expensive-calc.ava.js` on how to write such test and print the gas breakdown. If it involves create promises or cross contract calls, printing the gas breakdown is a little bit more complex, you can refer to `bench/__tests__/test-deploy-contract.ava.js` for the recipe. ## Details of size benchmark ### JS Contract ``` -rwxrwxr-x 1 bo bo 1009K Feb 9 10:49 ./build/deploy-contract.wasm -rwxrwxr-x 1 bo bo 506K Feb 8 12:11 ./build/expensive-calc.wasm -rwxrwxr-x 1 bo bo 512K Feb 7 15:57 ./build/highlevel-collection.wasm -rwxrwxr-x 1 bo bo 505K Feb 7 10:53 ./build/highlevel-minimal.wasm -rwxrwxr-x 1 bo bo 502K Feb 10 11:32 ./build/lowlevel-api.wasm -rwxrwxr-x 1 bo bo 502K Feb 10 11:47 ./build/lowlevel-minimal.wasm ``` ### Rust Contract ``` -rwxrwxr-x 1 bo bo 509K Feb 10 10:02 ./res/deploy_contract.wasm -rwxrwxr-x 1 bo bo 306K Feb 8 12:18 ./res/expensive_calc.wasm -rwxrwxr-x 1 bo bo 320K Feb 8 11:26 ./res/highlevel_collection.wasm -rwxrwxr-x 1 bo bo 160K Feb 7 10:51 ./res/highlevel_minimal.wasm -rwxrwxr-x 1 bo bo 387 Feb 7 11:56 ./res/lowlevel_api.wasm -rwxrwxr-x 1 bo bo 219 Feb 7 10:33 ./res/lowlevel_minimal.wasm ``` ## Appendix - Source code of the rust benchmark: https://github.com/near/sdk-rs-gas-benchmark ================================================ FILE: benchmark/__tests__/test-collections-performance.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logTotalGas, randomInt } from "./util.js"; const COLLECTION_SIZE = 20; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contracts. const lookupMapContract = await root.devDeploy("build/lookup-map.wasm"); const lookupSetContract = await root.devDeploy("build/lookup-set.wasm"); const unorderedMapContract = await root.devDeploy("build/unordered-map.wasm"); const unorderedSetContract = await root.devDeploy("build/unordered-set.wasm"); const vectorContract = await root.devDeploy("build/vector.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lookupMapContract, lookupSetContract, unorderedMapContract, unorderedSetContract, vectorContract, ali, }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("JS lookup map contract operations", async (t) => { const { ali, lookupMapContract } = t.context.accounts; let rAdd; for (let i = 0; i < COLLECTION_SIZE; i++) { rAdd = await ali.callRaw(lookupMapContract, "addElement", { key: i, value: i }); } t.is(rAdd.result.status.SuccessValue, ""); logTotalGas("Add element", rAdd, t); const val = randomInt(COLLECTION_SIZE); const rGet = await ali.callRaw(lookupMapContract, "getElement", { key: val }); t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val); logTotalGas("Get element", rGet, t); const rRem = await ali.callRaw(lookupMapContract, "removeElement", { key: randomInt(COLLECTION_SIZE) }); t.is(rRem.result.status.SuccessValue, ""); logTotalGas("Remove element", rRem, t); }); test("JS lookup set contract operations", async (t) => { const { ali, lookupSetContract } = t.context.accounts; let rAdd; for (let i = 0; i < COLLECTION_SIZE; i++) { rAdd = await ali.callRaw(lookupSetContract, "addElement", { value: i }); } t.is(rAdd.result.status.SuccessValue, ""); logTotalGas("Add element", rAdd, t); const rGet = await ali.callRaw(lookupSetContract, "containsElement", { value: randomInt(COLLECTION_SIZE) }); t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), true); logTotalGas("Get element", rGet, t); const rRem = await ali.callRaw(lookupSetContract, "removeElement", { value: randomInt(COLLECTION_SIZE) }); t.is(rRem.result.status.SuccessValue, ""); logTotalGas("Remove element", rRem, t); }); test("JS unordered map contract operations", async (t) => { const { ali, unorderedMapContract } = t.context.accounts; let rAdd; for (let i = 0; i < COLLECTION_SIZE; i++) { rAdd = await ali.callRaw(unorderedMapContract, "addElement", { key: i, value: i }); } t.is(rAdd.result.status.SuccessValue, ""); logTotalGas("Add element", rAdd, t); const val = randomInt(COLLECTION_SIZE); const rGet = await ali.callRaw(unorderedMapContract, "getElement", { key: val }); t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val); logTotalGas("Get element", rGet, t); const rIt = await ali.callRaw(unorderedMapContract, "iterate", {}); t.is(rIt.result.status.SuccessValue, ""); logTotalGas("Iterate collection", rIt, t); const rRem = await ali.callRaw(unorderedMapContract, "removeElement", { key: randomInt(COLLECTION_SIZE) }); t.is(rRem.result.status.SuccessValue, ""); logTotalGas("Remove element", rRem, t); }); test("JS unordered set contract operations", async (t) => { const { ali, unorderedSetContract } = t.context.accounts; let rAdd; for (let i = 0; i < COLLECTION_SIZE; i++) { rAdd = await ali.callRaw(unorderedSetContract, "addElement", { value: i }); } t.is(rAdd.result.status.SuccessValue, ""); logTotalGas("Add element", rAdd, t); const rGet = await ali.callRaw(unorderedSetContract, "containsElement", { value: randomInt(COLLECTION_SIZE) }); t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), true); logTotalGas ("Get element", rGet, t); const rIt = await ali.callRaw(unorderedSetContract, "iterate", {}); t.is(rIt.result.status.SuccessValue, ""); logTotalGas("Iterate collection", rIt, t); const rRem = await ali.callRaw(unorderedSetContract, "removeElement", { value: randomInt(COLLECTION_SIZE) }); t.is(rRem.result.status.SuccessValue, ""); logTotalGas("Remove element", rRem, t); }); test("JS vector contract operations", async (t) => { const { ali, vectorContract } = t.context.accounts; let rAdd; for (let i = 0; i < COLLECTION_SIZE; i++) { rAdd = await ali.callRaw(vectorContract, "addElement", { value: i }); } t.is(rAdd.result.status.SuccessValue, ""); logTotalGas("Add element", rAdd, t); const val = randomInt(COLLECTION_SIZE); const rGet = await ali.callRaw(vectorContract, "getElement", { index: val }); t.is(JSON.parse(Buffer.from(rGet.result.status.SuccessValue, "base64")), val); logTotalGas("Get element", rGet, t); const rIt = await ali.callRaw(vectorContract, "iterate", {}); t.is(rIt.result.status.SuccessValue, ""); logTotalGas("Iterate collection", rIt, t); const rRem = await ali.callRaw(vectorContract, "removeElement", { index: randomInt(COLLECTION_SIZE) }); t.is(rRem.result.status.SuccessValue, ""); logTotalGas("Remove element", rRem, t); }); ================================================ FILE: benchmark/__tests__/test-deploy-contract.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { formatGas, gasBreakdown, logGasBreakdown, logGasDetail, } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const callerContract = await root.createSubAccount("caller", { initialBalance: "1000N", }); await callerContract.deploy("build/deploy-contract.wasm"); const callerContractRs = await root.createSubAccount("callrs", { initialBalance: "1000N", }); await callerContractRs.deploy("res/deploy_contract.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, callerContract, ali, bob, carl, callerContractRs, }; }); test("JS promise batch deploy contract and call", async (t) => { const { bob, callerContract } = t.context.accounts; let r = await bob.callRaw(callerContract, "deploy_contract", "", { gas: "300 Tgas", }); // console.log(JSON.stringify(r, null, 2)); let deployed = callerContract.getSubAccount("a"); t.deepEqual(JSON.parse(Buffer.from(r.result.status.SuccessValue, "base64")), { currentAccountId: deployed.accountId, signerAccountId: bob.accountId, predecessorAccountId: callerContract.accountId, input: "abc", }); t.log( "Gas used to convert transaction to receipt: ", formatGas(r.result.transaction_outcome.outcome.gas_burnt) ); t.log( "Gas used to execute the receipt (actual contract call): ", formatGas(r.result.receipts_outcome[0].outcome.gas_burnt) ); let map = gasBreakdown(r.result.receipts_outcome[0].outcome); logGasBreakdown(map, t); t.log( "Gas used to execute the cross contract call: ", formatGas(r.result.receipts_outcome[1].outcome.gas_burnt) ); map = gasBreakdown(r.result.receipts_outcome[1].outcome); logGasBreakdown(map, t); t.log( "Gas used to refund unused gas for cross contract call: ", formatGas(r.result.receipts_outcome[2].outcome.gas_burnt) ); t.log( "Gas used to refund unused gas: ", // TODO: fix after near-workspaces is updated formatGas(r.result.receipts_outcome[3]?.outcome.gas_burnt || 0) ); t.log( "Total gas used: ", formatGas( r.result.transaction_outcome.outcome.gas_burnt + r.result.receipts_outcome[0].outcome.gas_burnt + r.result.receipts_outcome[1].outcome.gas_burnt + r.result.receipts_outcome[2].outcome.gas_burnt + // TODO: fix after near-workspaces is updated (r.result.receipts_outcome[3]?.outcome.gas_burnt || 0) ) ); }); test("RS promise batch deploy contract and call", async (t) => { const { bob, callerContractRs } = t.context.accounts; let r = await bob.callRaw(callerContractRs, "deploy_contract", "", { gas: "300 Tgas", }); // console.log(JSON.stringify(r, null, 2)); let deployed = callerContractRs.getSubAccount("a"); t.deepEqual(JSON.parse(Buffer.from(r.result.status.SuccessValue, "base64")), { currentAccountId: deployed.accountId, signerAccountId: bob.accountId, predecessorAccountId: callerContractRs.accountId, input: "abc", }); t.log( "Gas used to convert transaction to receipt: ", formatGas(r.result.transaction_outcome.outcome.gas_burnt) ); t.log( "Gas used to execute the receipt (actual contract call): ", formatGas(r.result.receipts_outcome[0].outcome.gas_burnt) ); let map = gasBreakdown(r.result.receipts_outcome[0].outcome); logGasBreakdown(map, t); t.log( "Gas used to execute the cross contract call: ", formatGas(r.result.receipts_outcome[1].outcome.gas_burnt) ); map = gasBreakdown(r.result.receipts_outcome[1].outcome); logGasBreakdown(map, t); t.log( "Gas used to refund unused gas for cross contract call: ", formatGas(r.result.receipts_outcome[2].outcome.gas_burnt) ); t.log( "Gas used to refund unused gas: ", // TODO: fix after near-workspaces is updated formatGas(r.result.receipts_outcome[3]?.outcome.gas_burnt || 0) ); t.log( "Total gas used: ", formatGas( r.result.transaction_outcome.outcome.gas_burnt + r.result.receipts_outcome[0].outcome.gas_burnt + r.result.receipts_outcome[1].outcome.gas_burnt + r.result.receipts_outcome[2].outcome.gas_burnt + // TODO: fix after near-workspaces is updated (r.result.receipts_outcome[3]?.outcome.gas_burnt || 0) ) ); }); ================================================ FILE: benchmark/__tests__/test-expensive-calc.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logGasDetail } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const expensiveContract = await root.devDeploy("build/expensive-calc.wasm"); const expensiveContractRs = await root.devDeploy("res/expensive_calc.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, expensiveContract, expensiveContractRs, ali, bob, carl, }; }); test("JS expensive contract, iterate 100 times", async (t) => { const { bob, expensiveContract } = t.context.accounts; let r = await bob.callRaw(expensiveContract, "expensive", { n: 100 }); t.is(r.result.status.SuccessValue, "LTUw"); logGasDetail(r, t); }); test("RS expensive contract. iterate 100 times", async (t) => { const { bob, expensiveContractRs } = t.context.accounts; let r = await bob.callRaw(expensiveContractRs, "expensive", { n: 100 }); t.is(r.result.status.SuccessValue, "LTUw"); logGasDetail(r, t); }); test("JS expensive contract, iterate 10000 times", async (t) => { const { bob, expensiveContract } = t.context.accounts; let r = await bob.callRaw( expensiveContract, "expensive", { n: 10000 }, { gas: BigInt(300 * 10 ** 12) } ); t.is(r.result.status.SuccessValue, "LTUwMDA="); logGasDetail(r, t); }); test("RS expensive contract. iterate 10000 times", async (t) => { const { bob, expensiveContractRs } = t.context.accounts; let r = await bob.callRaw(expensiveContractRs, "expensive", { n: 10000 }); t.is(r.result.status.SuccessValue, "LTUwMDA="); logGasDetail(r, t); }); test("JS expensive contract, iterate 20000 times", async (t) => { const { bob, expensiveContract } = t.context.accounts; let r = await bob.callRaw( expensiveContract, "expensive", { n: 20000 }, { gas: BigInt(300 * 10 ** 12) } ); t.is(r.result.status.SuccessValue, "LTEwMDAw"); logGasDetail(r, t); }); test("RS expensive contract. iterate 20000 times", async (t) => { const { bob, expensiveContractRs } = t.context.accounts; let r = await bob.callRaw(expensiveContractRs, "expensive", { n: 20000 }); t.is(r.result.status.SuccessValue, "LTEwMDAw"); logGasDetail(r, t); }); ================================================ FILE: benchmark/__tests__/test-highlevel-collection.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logGasDetail } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const highlevelContract = await root.devDeploy( "build/highlevel-collection.wasm" ); const highlevelContractRs = await root.devDeploy( "res/highlevel_collection.wasm" ); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, highlevelContract, highlevelContractRs, ali, bob, carl, }; }); test("JS highlevel collection contract", async (t) => { const { bob, highlevelContract } = t.context.accounts; let r = await bob.callRaw(highlevelContract, "set", { key: "a".repeat(100), value: "b".repeat(100), }); r = await bob.callRaw(highlevelContract, "set", { key: "b".repeat(100), value: "c".repeat(100), }); r = await bob.callRaw(highlevelContract, "set", { key: "c".repeat(100), value: "d".repeat(100), }); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); test("RS highlevel collection contract", async (t) => { const { bob, highlevelContractRs } = t.context.accounts; let r = await bob.callRaw(highlevelContractRs, "set", { key: "a".repeat(100), value: "b".repeat(100), }); r = await bob.callRaw(highlevelContractRs, "set", { key: "b".repeat(100), value: "c".repeat(100), }); r = await bob.callRaw(highlevelContractRs, "set", { key: "c".repeat(100), value: "d".repeat(100), }); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); ================================================ FILE: benchmark/__tests__/test-highlevel-minimal.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logGasDetail } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const highlevelContract = await root.devDeploy( "build/highlevel-minimal.wasm" ); const highlevelContractRs = await root.devDeploy( "res/highlevel_minimal.wasm" ); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, highlevelContract, highlevelContractRs, ali, bob, carl, }; }); test("JS highlevel minimal contract", async (t) => { const { bob, highlevelContract } = t.context.accounts; let r = await bob.callRaw(highlevelContract, "empty", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); test("RS highlevel minimal contract", async (t) => { const { bob, highlevelContractRs } = t.context.accounts; let r = await bob.callRaw(highlevelContractRs, "empty", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); ================================================ FILE: benchmark/__tests__/test-lowlevel-api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logGasDetail } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const lowlevelContract = await root.devDeploy("build/lowlevel-api.wasm"); const lowlevelContractRs = await root.devDeploy("res/lowlevel_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lowlevelContract, lowlevelContractRs, ali, bob, carl, }; }); test("JS lowlevel API contract", async (t) => { const { bob, lowlevelContract } = t.context.accounts; let r = await bob.callRaw(lowlevelContract, "lowlevel_storage_write", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); test("RS lowlevel API contract", async (t) => { const { bob, lowlevelContractRs } = t.context.accounts; let r = await bob.callRaw(lowlevelContractRs, "lowlevel_storage_write", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); test("JS lowlevel API contract, call many", async (t) => { const { bob, lowlevelContract } = t.context.accounts; let r = await bob.callRaw( lowlevelContract, "lowlevel_storage_write_many", "" ); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); ================================================ FILE: benchmark/__tests__/test-lowlevel-minimal.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import { logGasDetail } from "./util.js"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const lowlevelContract = await root.devDeploy("build/lowlevel-minimal.wasm"); const lowlevelContractRs = await root.devDeploy("res/lowlevel_minimal.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lowlevelContract, lowlevelContractRs, ali, bob, carl, }; }); test("JS lowlevel minimal contract", async (t) => { const { bob, lowlevelContract } = t.context.accounts; let r = await bob.callRaw(lowlevelContract, "empty", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); test("RS lowlevel minimal contract", async (t) => { const { bob, lowlevelContractRs } = t.context.accounts; let r = await bob.callRaw(lowlevelContractRs, "empty", ""); t.is(r.result.status.SuccessValue, ""); logGasDetail(r, t); }); ================================================ FILE: benchmark/__tests__/util.js ================================================ // Functions consumed by the benchmark contracts tests export function formatGas(gas) { if (gas < 10 ** 12) { let tGas = gas / 10 ** 12; let roundTGas = Math.round(tGas * 100000) / 100000; return roundTGas + "T"; } let tGas = gas / 10 ** 12; let roundTGas = Math.round(tGas * 100) / 100; return roundTGas + "T"; } export function gasBreakdown(outcome) { return new Map( outcome.metadata.gas_profile.map((g) => { return [g.cost, Number(g.gas_used)]; }) ); } export function logGasBreakdown(map, t) { map.forEach((v, k) => { t.log(" ", k, ": ", formatGas(v)); }); } export function logGasDetail(r, t) { t.log( "Gas used to convert transaction to receipt: ", formatGas(r.result.transaction_outcome.outcome.gas_burnt) ); t.log( "Gas used to execute the receipt (actual contract call): ", formatGas(r.result.receipts_outcome[0].outcome.gas_burnt) ); let map = gasBreakdown(r.result.receipts_outcome[0].outcome); logGasBreakdown(map, t); t.log( "Gas used to refund unused gas: ", // TODO: fix after near-workspaces is updated formatGas(r.result.receipts_outcome[1]?.outcome.gas_burnt || 0) ); t.log( "Total gas used: ", formatGas( r.result.transaction_outcome.outcome.gas_burnt + r.result.receipts_outcome[0].outcome.gas_burnt + // TODO: fix after near-workspaces is updated (r.result.receipts_outcome[1]?.outcome.gas_burnt || 0) ) ); } export function logTotalGas(prefix = '', r, t) { t.log( prefix + ' - Total gas used: ', formatGas( r.result.transaction_outcome.outcome.gas_burnt + r.result.receipts_outcome[0].outcome.gas_burnt + (r.result.receipts_outcome[1]?.outcome.gas_burnt || 0) ) ); } export function randomInt(max) { return Math.floor(Math.random() * max); } ================================================ FILE: benchmark/ava.config.cjs ================================================ require("util").inspect.defaultOptions.depth = 5; // Increase AVA's printing depth module.exports = { timeout: "300000", files: ["**/*.ava.js"], failWithoutAssertions: false, extensions: ["js"], }; ================================================ FILE: benchmark/example-outcome.json ================================================ { "result": { "receipts_outcome": [ { "block_hash": "B5zQjLFz1zu36xoUd37Bghzeanw8YfbkLk9amcVdPX4H", "id": "7GaMgKm53Mv5hwimyLTQnxEvaacvSABHhhJXbQYFkiXi", "outcome": { "executor_id": "dev-14843.test.near", "gas_burnt": 4217783714059, "logs": [], "metadata": { "gas_profile": [ { "cost": "CONTRACT_LOADING_BASE", "cost_category": "WASM_HOST_COST", "gas_used": "35445963" }, { "cost": "CONTRACT_LOADING_BYTES", "cost_category": "WASM_HOST_COST", "gas_used": "108947436750" }, { "cost": "WASM_INSTRUCTION", "cost_category": "WASM_HOST_COST", "gas_used": "1680864179808" } ], "version": 1 }, "receipt_ids": ["8FYdTYgNnz5jnCDFFHfbyrBVq5rp9beStZYjxjrXvQ5F"], "status": { "SuccessValue": "" }, "tokens_burnt": "4217783714059000000000" }, "proof": [] }, { "block_hash": "4oUZamxqDyUuMp47xfGE7FTnRBRC28VdMuPCS3oSCKpw", "id": "8FYdTYgNnz5jnCDFFHfbyrBVq5rp9beStZYjxjrXvQ5F", "outcome": { "executor_id": "bob.test.near", "gas_burnt": 223182562500, "logs": [], "metadata": { "gas_profile": [], "version": 1 }, "receipt_ids": [], "status": { "SuccessValue": "" }, "tokens_burnt": "0" }, "proof": [] } ], "status": { "SuccessValue": "" }, "transaction": { "actions": [ { "FunctionCall": { "args": "IiI=", "deposit": "0", "gas": 30000000000000, "method_name": "empty" } } ], "hash": "9TvjxGooYJ7A1Ju6NHocss2uCqPeUKiEv1EwPoiXUsS2", "nonce": 10000001, "public_key": "ed25519:5iyD5kpiWwBgW3vunXJdmj66ArJAZBhGUxHEud6UKbyr", "receiver_id": "dev-14843.test.near", "signature": "ed25519:5jb67kd3VoTnBWxpSgyHENdWerGwLaa5QJHaKVvaxrZosymia2cKf3DNC4ES3PHZ7mXHkky5JTheqnzzjCpop7fx", "signer_id": "bob.test.near" }, "transaction_outcome": { "block_hash": "FQAFEjJVuBJK6Mf64DxfpMPWifP8jyEUJc6zW3yn1rca", "id": "9TvjxGooYJ7A1Ju6NHocss2uCqPeUKiEv1EwPoiXUsS2", "outcome": { "executor_id": "bob.test.near", "gas_burnt": 2427936651538, "logs": [], "metadata": { "gas_profile": null, "version": 1 }, "receipt_ids": ["7GaMgKm53Mv5hwimyLTQnxEvaacvSABHhhJXbQYFkiXi"], "status": { "SuccessReceiptId": "7GaMgKm53Mv5hwimyLTQnxEvaacvSABHhhJXbQYFkiXi" }, "tokens_burnt": "2427936651538000000000" }, "proof": [] } }, "startMs": 1675654730297, "endMs": 1675654732818, "config": { "network": "sandbox", "rootAccountId": "test.near", "rpcAddr": "http://localhost:6163", "initialBalance": "100000000000000000000000000", "homeDir": "/tmp/sandbox/7a047a19-1712-48ba-9704-d5831cf91f2a", "port": 6163, "rm": false, "refDir": null } } ================================================ FILE: benchmark/jsconfig.json ================================================ { "compilerOptions": { "experimentalDecorators": true }, "exclude": ["node_modules"] } ================================================ FILE: benchmark/package.json ================================================ { "name": "bench", "version": "1.0.0", "description": "near-sdk-js benchmark", "main": "index.js", "type": "module", "scripts": { "build": "run-s build:*", "build:lowlevel-minimal": "near-sdk-js build src/lowlevel-minimal.js build/lowlevel-minimal.wasm", "build:highlevel-minimal": "near-sdk-js build src/highlevel-minimal.js build/highlevel-minimal.wasm", "build:lowlevel-api": "near-sdk-js build src/lowlevel-api.js build/lowlevel-api.wasm", "build:highlevel-collection": "near-sdk-js build src/highlevel-collection.js build/highlevel-collection.wasm", "build:expensive-calc": "near-sdk-js build src/expensive-calc.js build/expensive-calc.wasm", "build:deploy-contract": "near-sdk-js build src/deploy-contract.js build/deploy-contract.wasm", "build:lookup-map": "near-sdk-js build src/lookup-map.js build/lookup-map.wasm", "build:lookup-set": "near-sdk-js build src/lookup-set.js build/lookup-set.wasm", "build:unordered-map": "near-sdk-js build src/unordered-map.js build/unordered-map.wasm", "build:unordered-set": "near-sdk-js build src/unordered-set.js build/unordered-set.wasm", "build:vector": "near-sdk-js build src/vector.js build/vector.wasm", "test": "ava", "test:lowlevel-minimal": "ava __tests__/test-lowlevel-minimal.ava.js", "test:highlevel-minimal": "ava __tests__/test-highlevel-minimal.ava.js", "test:lowlevel-api": "ava __tests__/test-lowlevel-api.ava.js", "test:highlevel-collection": "ava __tests__/test-highlevel-collection.ava.js", "test:expensive-calc": "ava __tests__/test-expensive-calc.ava.js", "test:deploy-contract": "ava __tests__/test-deploy-contract.ava.js", "test:collections": "ava __tests__/test-collections-performance.ava.js" }, "author": "Near Inc ", "license": "Apache-2.0", "devDependencies": { "ava": "4.3.3", "near-workspaces": "4.0.0", "npm-run-all": "4.1.5" }, "dependencies": { "typescript": "4.7.4", "near-sdk-js": "workspace:*" } } ================================================ FILE: benchmark/src/deploy-contract.js ================================================ import { near } from "near-sdk-js"; /** * Used for contract deployment. More information for that * can be found in the README.md * - Deploy and cross contract call */ export function deploy_contract() { let promiseId = near.promiseBatchCreate("a.caller.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseBatchActionDeployContract( promiseId, includeBytes("../../tests/build/promise_api.wasm") ); near.promiseBatchActionFunctionCall( promiseId, "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); near.promiseReturn(promiseId); } ================================================ FILE: benchmark/src/expensive-calc.js ================================================ import { NearBindgen, call, near } from "near-sdk-js"; /** * ExpensiveCalc is connected to the expensive contract. More information for that * can be found in the README.md * - Computational expensive contract */ @NearBindgen({}) export class ExpensiveCalc { @call({}) expensive({ n }) { let ret = 0; let sign = 1; for (let i = 0; i < n; i++) { ret += i * sign; sign *= -1; } near.valueReturn(ret.toString()); } } ================================================ FILE: benchmark/src/highlevel-collection.js ================================================ import { NearBindgen, call, UnorderedMap } from "near-sdk-js"; /** * More information for that can be found in the README.md * - Highlevel collection */ @NearBindgen({}) export class HighlevelCollection { constructor() { this.unorderedMap = new UnorderedMap("a"); } @call({}) set({ key, value }) { this.unorderedMap.set(key, value); } } ================================================ FILE: benchmark/src/highlevel-minimal.js ================================================ import { NearBindgen, call } from "near-sdk-js"; /** * More information for that can be found in the README.md * - A highlevel minimal contract (using nearbindgen) */ @NearBindgen({}) export class HighlevelMinimal { @call({}) empty({}) {} } ================================================ FILE: benchmark/src/lookup-map.js ================================================ import { NearBindgen, call, LookupMap, view } from "near-sdk-js"; @NearBindgen({}) export class LookupMapContract { constructor() { this.lookupMap = new LookupMap("LM"); } @call({}) addElement({ key, value }) { this.lookupMap.set(key, value); } @call({}) removeElement({ key }) { this.lookupMap.remove(key); } @view({}) getElement({ key }) { return this.lookupMap.get(key); } } ================================================ FILE: benchmark/src/lookup-set.js ================================================ import { NearBindgen, call, LookupSet, view } from "near-sdk-js"; @NearBindgen({}) export class LookupSetContract { constructor() { this.lookupSet = new LookupSet("LS"); } @call({}) addElement({ value }) { this.lookupSet.set(value); } @call({}) removeElement({ value }) { this.lookupSet.remove(value); } @view({}) containsElement({ value }) { return this.lookupSet.contains(value); } } ================================================ FILE: benchmark/src/lowlevel-api.js ================================================ import { near } from "near-sdk-js"; /** * Helper method for the low level api. More information for that can be found in the README.md * - Low level API */ export function lowlevel_storage_write() { let data = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); near.storageWriteRaw(data, data); } /** * Helper method for the low level api. More information for that can be found in the README.md * - Low level API */ export function lowlevel_storage_write_many() { let data = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); near.storageWriteRaw(data, data); } ================================================ FILE: benchmark/src/lowlevel-minimal.js ================================================ import { near } from "near-sdk-js"; /** * More information for that can be found in the README.md * - A minimal contract */ export function empty() {} ================================================ FILE: benchmark/src/unordered-map.js ================================================ import { NearBindgen, call, UnorderedMap, view } from "near-sdk-js"; @NearBindgen({}) export class UnorderedMapContract { constructor() { this.unorderedMap = new UnorderedMap("UM"); } @call({}) addElement({ key, value }) { this.unorderedMap.set(key, value); } @call({}) removeElement({ key }) { this.unorderedMap.remove(key); } @view({}) getElement({ key }) { return this.unorderedMap.get(key); } @view({}) iterate() { const size = this.unorderedMap.length; for (let i = 0; i < size; i++) { this.unorderedMap.get(i); } } } ================================================ FILE: benchmark/src/unordered-set.js ================================================ import { NearBindgen, call, UnorderedSet, view } from "near-sdk-js"; @NearBindgen({}) export class UnorderedSetContract { constructor() { this.unorderedSet = new UnorderedSet("US"); } @call({}) addElement({ value }) { this.unorderedSet.set(value); } @call({}) removeElement({ value }) { this.unorderedSet.remove(value); } @view({}) containsElement({ value }) { return this.unorderedSet.contains(value); } @view({}) iterate() { const size = this.unorderedSet.length; for (let i = 0; i < size; i++) { this.unorderedSet.contains(i); } } } ================================================ FILE: benchmark/src/vector.js ================================================ import { NearBindgen, call, Vector, view } from "near-sdk-js"; @NearBindgen({}) export class VectorContract { constructor() { this.vector = new Vector("V"); } @call({}) addElement({ value }) { this.vector.push(value); } @call({}) removeElement({ index }) { this.vector.swapRemove(index); } @view({}) getElement({ index }) { return this.vector.get(index); } @view({}) iterate() { const size = this.vector.length; for (let i = 0; i < size; i++) { this.vector.get(i); } } } ================================================ FILE: benchmark/tsconfig.json ================================================ { "compilerOptions": { "moduleResolution": "node", "experimentalDecorators": true, "target": "es2020", "noEmit": true }, "exclude": ["node_modules"] } ================================================ FILE: examples/.gitignore ================================================ build node_modules ================================================ FILE: examples/README.md ================================================ # Example Smart Contracts This directory contains example smart contracts that demonstrate various functionalities using the `near-sdk-js`. These examples are meant to serve as a starting point for developers looking to build smart contracts on the NEAR blockchain using JavaScript/TypeScript. ## Overview The example smart contracts provided here showcase different use cases and features of the NEAR blockchain. Each contract is written in JavaScript/TypeScript and demonstrates best practices for developing secure, efficient, and scalable smart contracts. ## Build and Test To build and test the smart contracts in this directory, follow the steps below: ### Build To build all the smart contracts, run the following command in the root of the project: ``` pnpm build ``` ### Test To run the tests for the example smart contracts, use the following command: ``` pnpm test ``` This command will execute the test suites associated with each smart contract, ensuring that they function as expected. ## List of Example Smart Contracts The following smart contracts demonstrate various capabilities of the NEAR blockchain, using JavaScript/TypeScript. Each example highlights specific features and serves as a practical guide for developers. **[basic-updates](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/basic-updates)**
A simple smart contract that can make basic state updates. **[counter](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/counter)**
A simple smart contract that demonstrates state management by implementing a basic counter with increment and decrement functionalities. **[cross-contract](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/cross-contract)**
A smart contract that demonstrates how to perform cross-contract calls on the NEAR blockchain, including querying external contracts to manage on-chain interactions. **[fungible-token](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/fungible-token)**
Example implementation of a Fungible Token (FT) contract using the [near-contract-standards](https://github.com/near/near-sdk-js/tree/develop/packages/near-contract-standards), including `storage_management`. **[non-fungible-token](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/non-fungible-token)**
Example implementation of a Non-Fungible Token (NFT) contract using the [near-contract-standards](https://github.com/near/near-sdk-js/tree/develop/packages/near-contract-standards). **[programmatic-updates](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/programmatic-updates)**
Contracts showcasing how contracts can evolve while maintaining certain logic from previous versions. **[state-migration](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/state-migration)**
A smart contract that demonstrates how to handle state migration in a NEAR smart contract. **[status-message](https://github.com/near/near-sdk-js/tree/documentation-improvements/examples/src/status-message)**
Smart contracts that record the status messages of the accounts that call the contracts. ## Contributing If you find any issues or have suggestions for improvement, feel free to open an issue or submit a pull request. Contributions are welcome! ================================================ FILE: examples/__tests__/standard-ft/ft-tests.ava.js ================================================ import { NEAR, Worker } from "near-workspaces"; import test from "ava"; const INITIAL_BALANCE = NEAR.parse("10000 N").toJSON(); const ONE_YOCTO = "1"; const STOARAGE_BYTE_COST = 10_000_000_000_000_000_000n; const ACCOUNT_STORAGE_BALANCE = String(STOARAGE_BYTE_COST * 138n); test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const ftContract = await root.devDeploy("./build/my-ft.wasm"); await ftContract.call( ftContract, "init_with_default_meta", { owner_id: ftContract.accountId, total_supply: INITIAL_BALANCE } ); /** * DEFI contract implemented in https://github.com/near/near-sdk-rs/tree/master/examples/fungible-token/test-contract-defi * Iterface: * pub fn new(fungible_token_account_id: AccountId) -> Self; * fn ft_on_transfer( &mut self, sender_id: AccountId, amount: U128, msg: String, ) -> PromiseOrValue * If given `msg: "take-my-money", immediately returns U128::From(0). Otherwise, makes a cross-contract call to own `value_please` function, passing `msg` value_please will attempt to parse `msg` as an integer and return a U128 version of it */ const defiContract = await root.devDeploy("./res/defi.wasm"); await defiContract.call( defiContract, "new", { fungible_token_account_id: ftContract.accountId } ); const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("10 N").toJSON() }); await registerUser(ftContract, alice.accountId); t.context.worker = worker; t.context.accounts = { root, ftContract, alice, defiContract, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); async function registerUser(contract, account_id) { const deposit = String(ACCOUNT_STORAGE_BALANCE); await contract.call(contract, "storage_deposit", { account_id: account_id }, { attachedDeposit: deposit }); } test("test_total_supply", async (t) => { const { ftContract } = t.context.accounts; const res = await ftContract.view("ft_total_supply", {}); t.is(BigInt(res), BigInt(INITIAL_BALANCE)); }); test("test_storage_deposit", async (t) => { const { ftContract, root } = t.context.accounts; const bob = await root.createSubAccount("bob", { initialBalance: NEAR.parse("10 N").toJSON() }); await registerUser(ftContract, bob.accountId); const bobStorageBalance = await ftContract.view("storage_balance_of", { account_id: bob.accountId }); t.is(bobStorageBalance.total, String(ACCOUNT_STORAGE_BALANCE)); }); test("test_simple_transfer", async (t) => { const TRANSFER_AMOUNT = NEAR.parse("1000 N").toJSON(); const EXPECTED_ROOT_BALANCE = NEAR.parse("9000 N").toJSON(); const { ftContract, alice } = t.context.accounts; await ftContract.call( ftContract, "ft_transfer", { receiver_id: alice.accountId, amount: TRANSFER_AMOUNT, memo: null }, { attachedDeposit: ONE_YOCTO } ); let root_balance = await ftContract.view("ft_balance_of", { account_id: ftContract.accountId }); let alice_balance = await ftContract.view("ft_balance_of", { account_id: alice.accountId }); t.is(EXPECTED_ROOT_BALANCE, root_balance); t.is(TRANSFER_AMOUNT, alice_balance); }); test("test_close_account_empty_balance", async (t) => { const { ftContract, alice } = t.context.accounts; let res = await alice.call(ftContract, "storage_unregister", {}, { attachedDeposit: ONE_YOCTO }); t.is(res, true); // TODO: doublecheck }); test("test_close_account_non_empty_balance", async (t) => { const { ftContract } = t.context.accounts; try { await ftContract.call(ftContract, "storage_unregister", {}, { attachedDeposit: ONE_YOCTO }); throw Error("Unreachable string"); } catch (e) { t.is(JSON.stringify(e, Object.getOwnPropertyNames(e)).includes("Can't unregister the account with the positive balance without force"), true); } try { await ftContract.call(ftContract, "storage_unregister", { force: false }, { attachedDeposit: ONE_YOCTO }); throw Error("Unreachable string"); } catch (e) { t.is(JSON.stringify(e, Object.getOwnPropertyNames(e)).includes("Can't unregister the account with the positive balance without force"), true); } }); test("simulate_close_account_force_non_empty_balance", async (t) => { const { ftContract } = t.context.accounts; await ftContract.call( ftContract, "storage_unregister", { force: true }, { attachedDeposit: ONE_YOCTO } ); const res = await ftContract.view("ft_total_supply", {}); t.is(res, "0"); }); test("simulate_transfer_call_with_burned_amount", async (t) => { const TRANSFER_AMOUNT = NEAR.parse("100 N").toJSON(); const { ftContract, defiContract } = t.context.accounts; // defi contract must be registered as a FT account await registerUser(ftContract, defiContract.accountId); const result = await ftContract .batch(ftContract) .functionCall( 'ft_transfer_call', { receiver_id: defiContract.accountId, amount: TRANSFER_AMOUNT, memo: null, msg: "10", }, { attachedDeposit: '1', gas: '150 Tgas' }, ) .functionCall( 'storage_unregister', { force: true }, { attachedDeposit: '1', gas: '150 Tgas', }, ) .transact(); const logs = JSON.stringify(result); let expected = `Account @${ftContract.accountId} burned ${10}`; t.is(logs.includes("The account of the sender was deleted"), true); t.is(logs.includes(expected), true); const new_total_supply = await ftContract.view("ft_total_supply", {}); t.is(BigInt(new_total_supply), BigInt(TRANSFER_AMOUNT) - 10n); const defi_balance = await ftContract.view("ft_balance_of", { account_id: defiContract.accountId }); t.is(BigInt(defi_balance), BigInt(TRANSFER_AMOUNT) - 10n); }); test("simulate_transfer_call_with_immediate_return_and_no_refund", async (t) => { const TRANSFER_AMOUNT = NEAR.parse("100 N").toJSON(); const { ftContract, defiContract } = t.context.accounts; // defi ftContract must be registered as a FT account await registerUser(ftContract, defiContract.accountId); // root invests in defi by calling `ft_transfer_call` await ftContract.call( ftContract, "ft_transfer_call", { receiver_id: defiContract.accountId, amount: TRANSFER_AMOUNT, memo: null, msg: "take-my-money" }, { attachedDeposit: ONE_YOCTO, gas: 300000000000000, } ); let root_balance = await ftContract.view("ft_balance_of", { account_id: ftContract.accountId }); let defi_balance = await ftContract.view("ft_balance_of", { account_id: defiContract.accountId }); t.is(BigInt(INITIAL_BALANCE) - BigInt(TRANSFER_AMOUNT), BigInt(root_balance)); t.is(TRANSFER_AMOUNT, defi_balance); }); test("simulate_transfer_call_when_called_contract_not_registered_with_ft", async (t) => { const TRANSFER_AMOUNT = NEAR.parse("100 N").toJSON(); const { ftContract, defiContract } = t.context.accounts; // call fails because DEFI contract is not registered as FT user try { await ftContract.call( ftContract, "ft_transfer_call", { receiver_id: defiContract.accountId, amount: TRANSFER_AMOUNT, memo: null, msg: "take-my-money" }, { attachedDeposit: ONE_YOCTO, gas: 50000000000000n, } ); t.is(true, false); // Unreachable } catch (e) { t.is(JSON.stringify(e, Object.getOwnPropertyNames(e)).includes("is not registered"), true); } // balances remain unchanged let root_balance = await ftContract.view("ft_balance_of", { account_id: ftContract.accountId }); let defi_balance = await ftContract.view("ft_balance_of", { account_id: defiContract.accountId }); t.is(BigInt(INITIAL_BALANCE), BigInt(root_balance)); t.is("0", defi_balance); }); test("simulate_transfer_call_with_promise_and_refund", async (t) => { const REFUND_AMOUNT = NEAR.parse("50 N").toJSON(); const TRANSFER_AMOUNT = NEAR.parse("100 N").toJSON(); const TRANSFER_CALL_GAS = String(300_000_000_000_000n); const { ftContract, defiContract } = t.context.accounts; // defi contract must be registered as a FT account await registerUser(ftContract, defiContract.accountId); await ftContract.call(ftContract, "ft_transfer_call", { receiver_id: defiContract.accountId, amount: TRANSFER_AMOUNT, memo: null, msg: REFUND_AMOUNT, }, { attachedDeposit: ONE_YOCTO, gas: TRANSFER_CALL_GAS, }); let root_balance = await ftContract.view("ft_balance_of", { account_id: ftContract.accountId }); let defi_balance = await ftContract.view("ft_balance_of", { account_id: defiContract.accountId }); t.is(BigInt(INITIAL_BALANCE) - BigInt(TRANSFER_AMOUNT) + BigInt(REFUND_AMOUNT), BigInt(root_balance)); t.is(BigInt(TRANSFER_AMOUNT) - BigInt(REFUND_AMOUNT), BigInt(defi_balance)); }); test("simulate_transfer_call_promise_panics_for_a_full_refund", async (t) => { const TRANSFER_AMOUNT = NEAR.parse("100 N").toJSON(); const { ftContract, defiContract } = t.context.accounts; // defi contract must be registered as a FT account await registerUser(ftContract, defiContract.accountId); // root invests in defi by calling `ft_transfer_call` const res = await ftContract.callRaw( ftContract, "ft_transfer_call", { receiver_id: defiContract.accountId, amount: TRANSFER_AMOUNT, memo: null, msg: "no parsey as integer big panic oh no", }, { attachedDeposit: ONE_YOCTO, gas: 50000000000000n, } ); t.is(JSON.stringify(res).includes("ParseIntError"), true); // balances remain unchanged let root_balance = await ftContract.view("ft_balance_of", { account_id: ftContract.accountId }); let defi_balance = await ftContract.view("ft_balance_of", { account_id: defiContract.accountId }); t.is(INITIAL_BALANCE, root_balance); t.is("0", defi_balance); }); ================================================ FILE: examples/__tests__/standard-nft/test_approval.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy status-message the contract. const nft = await root.devDeploy("./build/my-nft.wasm"); // Create test accounts const ali = await root.createSubAccount("alice"); const bob = await root.createSubAccount("bob"); const nftOwner = await root.createSubAccount("owner"); const nftReceiver = await root.devDeploy("./build/nft-receiver.wasm"); const approvalReceiver = await root.devDeploy( "./build/nft-approval-receiver.wasm" ); await nft.call(nft, "init", { owner_id: nftOwner.accountId, metadata: { spec: "nft-1.0.0", name: "My NFT", symbol: "NFT" }, }); await nftReceiver.call(nftReceiver, "init", { non_fungible_token_account_id: nft.accountId, }); await approvalReceiver.call(approvalReceiver, "init", { non_fungible_token_account_id: nft.accountId, }); let token_metadata = { title: "Olympus Mons", description: "The tallest mountain in the charted solar system", media: null, media_hash: null, copies: 1, issued_at: null, expires_at: null, starts_at: null, updated_at: null, extra: null, reference: null, reference_hash: null, }; await nftOwner.call( nft, "nft_mint", { token_id: "0", token_owner_id: nftOwner.accountId, token_metadata, }, { attachedDeposit: "10 mN" } ); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, nft, ali, bob, nftOwner, nftReceiver, approvalReceiver, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Simple approve", async (t) => { const { ali, bob, nft, nftOwner } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: ali.accountId, }, { attachedDeposit: "510000000000000000000" } ); t.assert(res.result.status.SuccessValue); let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, }); t.assert(alice_approved); let alice_approval_id_is_1 = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, approval_id: "1", }); t.assert(alice_approval_id_is_1); let alice_approval_id_is_2 = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, approval_id: "2", }); t.assert(!alice_approval_id_is_2); res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: ali.accountId, }, { attachedDeposit: "1", } ); t.assert(res.result.status.SuccessValue); alice_approval_id_is_2 = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, approval_id: "2", }); t.assert(alice_approval_id_is_2); res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: bob.accountId, }, { attachedDeposit: "550000000000000000000", } ); t.assert(res.result.status.SuccessValue); let bob_approval_id_is_3 = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, approval_id: "3", }); t.assert(bob_approval_id_is_3); }); test("Approve call", async (t) => { const { nft, nftOwner, approvalReceiver } = t.context.accounts; let res = await nftOwner.call( nft, "nft_approve", { token_id: "0", account_id: approvalReceiver.accountId, msg: "return-now", }, { attachedDeposit: "610000000000000000000", gas: "300 Tgas" } ); t.is(res, "cool"); res = await nftOwner.call( nft, "nft_approve", { token_id: "0", account_id: approvalReceiver.accountId, msg: "hahaha", }, { attachedDeposit: "1", gas: "300 Tgas" } ); t.is(res, "hahaha"); }); test("Approved account transfers token", async (t) => { const { ali, nft, nftOwner } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: ali.accountId, }, { attachedDeposit: "510000000000000000000" } ); t.assert(res.result.status.SuccessValue); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); res = await ali.callRaw( nft, "nft_transfer", { receiver_id: ali.accountId, token_id: "0", memo: "gotcha! bahahaha", }, { attachedDeposit: "1" } ); t.is(res.result.status.SuccessValue, ""); token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, ali.accountId); }); test("revoke", async (t) => { const { ali, bob, nft, nftOwner } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: ali.accountId, }, { attachedDeposit: "510000000000000000000" } ); t.assert(res.result.status.SuccessValue); res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", account_id: bob.accountId, }, { attachedDeposit: "510000000000000000000", } ); t.assert(res.result.status.SuccessValue); res = await nftOwner.callRaw( nft, "nft_revoke", { token_id: "0", account_id: ali.accountId, }, { attachedDeposit: "1", } ); t.is(res.result.status.SuccessValue, ""); let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, }); t.assert(!alice_approved); let bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, }); t.assert(bob_approved); res = await nftOwner.callRaw( nft, "nft_revoke", { token_id: "0", account_id: bob.accountId, }, { attachedDeposit: "1", } ); t.is(res.result.status.SuccessValue, ""); alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, }); t.assert(!alice_approved); bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, }); t.assert(!bob_approved); }); test("revoke all", async (t) => { const { ali, bob, nft, nftOwner } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", accountId: ali.accountId, }, { attachedDeposit: "510000000000000000000" } ); t.assert(res.result.status.SuccessValue); res = await nftOwner.callRaw( nft, "nft_approve", { token_id: "0", accountId: bob.accountId, }, { attachedDeposit: "510000000000000000000", } ); t.assert(res.result.status.SuccessValue); res = await nftOwner.callRaw( nft, "nft_revoke_all", { token_id: "0" }, { attachedDeposit: "1", } ); t.is(res.result.status.SuccessValue, ""); let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, }); t.assert(!alice_approved); let bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, }); t.assert(!bob_approved); }); ================================================ FILE: examples/__tests__/standard-nft/test_core.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; const MAX_GAS = 300_000_000_000_000n; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy status-message the contract. const nft = await root.devDeploy("./build/my-nft.wasm"); // Create test accounts const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const nftOwner = await root.createSubAccount("owner"); const nftReceiver = await root.devDeploy("./build/nft-receiver.wasm"); await nft.call(nft, "init", { owner_id: nftOwner.accountId, metadata: { spec: "nft-1.0.0", name: "My NFT", symbol: "NFT" }, }); await nftReceiver.call(nftReceiver, "init", { non_fungible_token_account_id: nft.accountId, }); let token_metadata = { title: "Olympus Mons", description: "The tallest mountain in the charted solar system", media: null, media_hash: null, copies: 1, issued_at: null, expires_at: null, starts_at: null, updated_at: null, extra: null, reference: null, reference_hash: null, }; await nftOwner.call( nft, "nft_mint", { token_id: "0", token_owner_id: nftOwner.accountId, token_metadata, }, { attachedDeposit: "10 mN" } ); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, nft, ali, bob, nftOwner, nftReceiver, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Simple transfer", async (t) => { const { ali, nft, nftOwner } = t.context.accounts; let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); let res = await nftOwner.callRaw( nft, "nft_transfer", { receiver_id: ali.accountId, token_id: "0", memo: "simple transfer", }, { attachedDeposit: "1" } ); t.is(res.result.status.SuccessValue, ""); t.is(res.logs.length, 1); token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, ali.accountId); }); test("Transfer call fast return to sender", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "return-it-now", }, { attachedDeposit: "1", gas: MAX_GAS } ); t.is( Buffer.from(res.result.status.SuccessValue, "base64").toString(), "false" ); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); }); test("Transfer call slow return to sender", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "return-it-later", }, { attachedDeposit: "1", gas: MAX_GAS } ); t.is( Buffer.from(res.result.status.SuccessValue, "base64").toString(), "false" ); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); }); test("Transfer call fast keep with sender", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "keep-it-now", }, { attachedDeposit: "1", gas: MAX_GAS } ); t.is( Buffer.from(res.result.status.SuccessValue, "base64").toString(), "true" ); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftReceiver.accountId); }); test("Transfer call slow keep with sender", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "keep-it-later", }, { attachedDeposit: "1", gas: MAX_GAS } ); t.is( Buffer.from(res.result.status.SuccessValue, "base64").toString(), "true" ); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftReceiver.accountId); }); test("Transfer call receiver panics", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "incorrect message", }, { attachedDeposit: "1", gas: MAX_GAS } ); t.is( Buffer.from(res.result.status.SuccessValue, "base64").toString(), "false" ); t.is(res.logs.length, 3); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); }); test("Transfer call receiver panics and nft_resolve_transfer produces no log if not enough gas", async (t) => { const { nft, nftOwner, nftReceiver } = t.context.accounts; let res = await nftOwner.callRaw( nft, "nft_transfer_call", { receiver_id: nftReceiver.accountId, token_id: "0", memo: "transfer & call", msg: "incorrect message", }, { attachedDeposit: "1", gas: 30_000_000_000_000n } ); t.assert(res.result.status.Failure !== undefined); t.is(res.logs.length, 0); let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); }); test("Simple transfer no logs on failure", async (t) => { const { nft, nftOwner } = t.context.accounts; let token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); // transfer to the current owner should fail and not print log let res = await nftOwner.callRaw( nft, "nft_transfer", { receiver_id: nftOwner.accountId, token_id: "0", memo: "simple transfer", }, { attachedDeposit: "1" } ); t.assert(res.result.status.Failure !== undefined); t.is(res.logs.length, 0); token = await nft.view("nft_token", { token_id: "0" }); t.is(token.owner_id, nftOwner.accountId); }); ================================================ FILE: examples/__tests__/standard-nft/test_enumeration.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; async function helper_mint(nft, nftOwner, id, title, description) { let token_metadata = { title, description, media: null, media_hash: null, copies: 1, issued_at: null, expires_at: null, starts_at: null, updated_at: null, extra: null, reference: null, reference_hash: null, }; await nftOwner.call( nft, "nft_mint", { token_id: id, token_owner_id: nftOwner.accountId, token_metadata, }, { attachedDeposit: "10 mN" } ); } test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy status-message the contract. const nft = await root.devDeploy("./build/my-nft.wasm"); // Create test accounts const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const nftOwner = await root.createSubAccount("owner"); const nftReceiver = await root.devDeploy("./build/nft-receiver.wasm"); await nft.call(nft, "init", { owner_id: nftOwner.accountId, metadata: { spec: "nft-1.0.0", name: "My NFT", symbol: "NFT" }, }); await nftReceiver.call(nftReceiver, "init", { non_fungible_token_account_id: nft.accountId, }); await helper_mint( nft, nftOwner, "0", "Olympus Mons", "The tallest mountain in the charted solar system" ); await helper_mint(nft, nftOwner, "1", "Black as the Night", "In charcoal"); await helper_mint(nft, nftOwner, "2", "Hamakua", "Vintage recording"); await helper_mint(nft, nftOwner, "3", "Aloha ke akua", "Original with piano"); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, nft, ali, bob, nftOwner, nftReceiver, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Enumerate NFT tokens total supply", async (t) => { const { nft } = t.context.accounts; let totalSupply = await nft.view("nft_total_supply"); t.is(totalSupply, 4); }); test("Enumerate NFT tokens", async (t) => { const { nft } = t.context.accounts; let nftTokens = await nft.view("nft_tokens", { from_index: 1 }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "1"); t.is(nftTokens[1].token_id, "2"); t.is(nftTokens[2].token_id, "3"); nftTokens = await nft.view("nft_tokens", { limit: 2 }); t.is(nftTokens.length, 2); t.is(nftTokens[0].token_id, "0"); t.is(nftTokens[1].token_id, "1"); }); test("Enumerate NFT tokens supply for owner", async (t) => { const { ali, nft, nftOwner } = t.context.accounts; let aliNfts = await nft.view("nft_supply_for_owner", { account_id: ali.accountId, }); t.is(aliNfts, 0); let ownerNfts = await nft.view("nft_supply_for_owner", { account_id: nftOwner.accountId, }); t.is(ownerNfts, 4); }); test("Enumerate NFT tokens for owner", async (t) => { const { ali, nft, nftOwner } = t.context.accounts; let nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, }); t.is(nftTokens.length, 4); t.is(nftTokens[0].token_id, "0"); t.is(nftTokens[1].token_id, "1"); t.is(nftTokens[2].token_id, "2"); t.is(nftTokens[3].token_id, "3"); nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, from_index: 1, }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "1"); t.is(nftTokens[1].token_id, "2"); t.is(nftTokens[2].token_id, "3"); nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, limit: 2, }); t.is(nftTokens.length, 2); t.is(nftTokens[0].token_id, "0"); t.is(nftTokens[1].token_id, "1"); let res = await nftOwner.callRaw( nft, "nft_transfer", { receiver_id: ali.accountId, token_id: "0", memo: "simple transfer", }, { attachedDeposit: "1" } ); t.is(res.result.status.SuccessValue, ""); nftTokens = await nft.view("nft_tokens_for_owner", { account_id: ali.accountId, }); t.is(nftTokens.length, 1); nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "3"); t.is(nftTokens[1].token_id, "1"); t.is(nftTokens[2].token_id, "2"); }); ================================================ FILE: examples/__tests__/test-basic-updates.ava.js ================================================ import {NEAR, Worker} from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const contract = await root.devDeploy("./build/basic-updates-base.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, contract, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("migration basic updates works", async (t) => { const { contract, ali } = t.context.accounts; await ali.call(contract, "add_message", { text: "hello" }, { attachedDeposit: NEAR.parse("1 N").toJSON() }); let message0 = await contract.view("get_message", { index: 0 }); let payment0 = await contract.view("get_payment", { index: 0 }); console.log("message0= ", message0," payment0=", payment0) t.assert( message0.text === "hello" && message0.premium && message0.sender === ali.accountId ); t.assert(payment0 == NEAR.parse("1 N")); await contract.deploy("./build/basic-updates-update.wasm"); await ali.call(contract, "migrateState", {}); let messageUpdated0 = await contract.view("get_message", { index: 0 }); console.log("messageUpdated0= ", messageUpdated0); t.assert( message0.text === messageUpdated0.text && message0.premium === messageUpdated0.premium && message0.sender === messageUpdated0.sender && payment0 == messageUpdated0.payment ); }); ================================================ FILE: examples/__tests__/test-clean-state.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the clean-state contract. const cleanState = await root.devDeploy("./build/clean-state.wasm"); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, cleanState, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Clean state after storing", async (t) => { const { root, cleanState } = t.context.accounts; await root.call(cleanState, "put", { key: "1", value: 1 }); const value1 = await cleanState.view("get", { key: "1" }); t.is(value1, "1"); await cleanState.call(cleanState, "clean", { keys: ["1"] }); const value2 = await cleanState.view("get", { key: "1" }); t.is(value2, null); }); ================================================ FILE: examples/__tests__/test-counter.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etc. const root = worker.rootAccount; // Deploy the counter contract. const counter = await root.devDeploy( process.env["COUNTER_LOWLEVEL"] ? "./build/counter-lowlevel.wasm" : process.env["COUNTER_TS"] ? "./build/counter-ts.wasm" : "./build/counter.wasm" ); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, counter, ali, bob }; }); // If the environment is reused, use test.after to replace test.afterEach test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Initial count is 0", async (t) => { const { counter } = t.context.accounts; const result = await counter.view("getCount", {}); t.is(result, 0); }); test("Increase works", async (t) => { const { counter, ali, bob } = t.context.accounts; await ali.call(counter, "increase", {}); let result = await counter.view("getCount", {}); t.is(result, 1); await bob.call(counter, "increase", { n: 4 }); result = await counter.view("getCount", {}); t.is(result, 5); }); test("Decrease works", async (t) => { const { counter, ali, bob } = t.context.accounts; await ali.call(counter, "decrease", {}); let result = await counter.view("getCount", {}); t.is(result, -1); let dec = await bob.callRaw(counter, "decrease", { n: 4 }); // ensure imported log does work, not silent failure t.is( dec.result.receipts_outcome[0].outcome.logs[0], "Counter decreased to -5" ); result = await counter.view("getCount", {}); t.is(result, -5); }); ================================================ FILE: examples/__tests__/test-cross-contract-call-loop.ava.js ================================================ import { Worker, NEAR } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const xccLoop = await root.createSubAccount("xcc-loop"); await xccLoop.deploy("./build/cross-contract-call-loop.wasm"); const firstContract = await root.createSubAccount("first-contract"); const secondContract = await root.createSubAccount("second-contract"); const thirdContract = await root.createSubAccount("third-contract"); await firstContract.deploy("./build/counter.wasm"); await secondContract.deploy("./build/counter.wasm"); await thirdContract.deploy("./build/counter.wasm"); await root.call(firstContract, "increase", {}); await root.call(secondContract, "increase", {}); await root.call(thirdContract, "increase", {}); const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("100 N").toJSON(), }); t.context.worker = worker; t.context.accounts = { root, alice, xccLoop }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("should have a count of 3 after calling incrementCount", async (t) => { const { xccLoop, alice } = t.context.accounts; const expected = 3; const callbackResult = await alice.call( xccLoop, "incrementCount", {}, { gas: "300" + "0".repeat(12) } ); t.is(callbackResult, 3); const result = await xccLoop.view("getCount"); t.deepEqual(result, expected); }); ================================================ FILE: examples/__tests__/test-cross-contract-call-ts.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy status-message the contract. const statusMessage = await root.devDeploy("./build/status-message.wasm"); // Deploy the onCall contract. const onCall = await root.devDeploy("./build/cross-contract-call-ts.wasm"); // Init the contract await onCall.call(onCall, "init", { statusMessageContract: statusMessage.accountId, }); // Create test accounts const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, statusMessage, onCall, ali, bob, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Nobody is on-call in the beginning", async (t) => { const { onCall } = t.context.accounts; const result = await onCall.view("person_on_call", {}); t.is(result, ""); }); test("Person can be set on-call if AVAILABLE", async (t) => { const { ali, bob, onCall, statusMessage } = t.context.accounts; // Ali set her status as AVAILABLE await ali.call(statusMessage, "set_status", { message: "AVAILABLE" }); // Bob sets Ali on-call await bob.call( onCall, "set_person_on_call", { accountId: ali.accountId }, { gas: 120000000000000 } ); // Check that Ali is on-call t.is(await onCall.view("person_on_call", {}), ali.accountId); }); test("Person can NOT be set on-call if UNAVAILABLE", async (t) => { const { ali, bob, onCall, statusMessage } = t.context.accounts; // Ali set her status as AVAILABLE await ali.call(statusMessage, "set_status", { message: "UNAVAILABLE" }); // Bob tries to sets Ali on-call await bob.call( onCall, "set_person_on_call", { accountId: ali.accountId }, { gas: 120000000000000 } ); // Check that Ali is NOT on-call t.not(await onCall.view("person_on_call", {}), ali.accountId); }); ================================================ FILE: examples/__tests__/test-cross-contract-call.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy status-message the contract. const statusMessage = await root.devDeploy("./build/status-message.wasm"); // Deploy the onCall contract. const onCall = await root.devDeploy("./build/cross-contract-call.wasm"); // Init the contract await onCall.call(onCall, "init", { statusMessageContract: statusMessage.accountId, }); // Create test accounts const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, statusMessage, onCall, ali, bob, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Nobody is on-call in the beginning", async (t) => { const { onCall } = t.context.accounts; const result = await onCall.view("person_on_call", {}); t.is(result, "undefined"); }); test("Person can be set on-call if AVAILABLE", async (t) => { const { ali, bob, onCall, statusMessage } = t.context.accounts; // Ali set her status as AVAILABLE await ali.call(statusMessage, "set_status", { message: "AVAILABLE" }); // Bob sets Ali on-call await bob.call( onCall, "set_person_on_call", { accountId: ali.accountId }, { gas: 120000000000000 } ); // Check that Ali is on-call t.is(await onCall.view("person_on_call", {}), ali.accountId); }); test("Person can NOT be set on-call if UNAVAILABLE", async (t) => { const { ali, bob, onCall, statusMessage } = t.context.accounts; // Ali set her status as AVAILABLE await ali.call(statusMessage, "set_status", { message: "UNAVAILABLE" }); // Bob tries to sets Ali on-call await bob.call( onCall, "set_person_on_call", { accountId: ali.accountId }, { gas: 120000000000000 } ); // Check that Ali is NOT on-call t.not(await onCall.view("person_on_call", {}), ali.accountId); }); ================================================ FILE: examples/__tests__/test-fungible-token-lockable.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the lockable-ft contract. const lockableFt = await root.devDeploy( "./build/fungible-token-lockable.wasm" ); // Init the contract await lockableFt.call(lockableFt, "init", { prefix: "prefix", totalSupply: 10000, }); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lockableFt, ali, bob }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Owner initial details", async (t) => { const { lockableFt } = t.context.accounts; const totalSupply = await lockableFt.view("getTotalSupply", {}); t.is(totalSupply, 10000); const totalBalance = await lockableFt.view("getTotalBalance", { ownerId: lockableFt.accountId, }); t.is(totalBalance, 10000); const unlockedBalance = await lockableFt.view("getUnlockedBalance", { ownerId: lockableFt.accountId, }); t.is(unlockedBalance, 10000); const allowance = await lockableFt.view("getAllowance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(allowance, 0); const lockedBalance = await lockableFt.view("getLockedBalance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(lockedBalance, 0); }); test("Set allowance", async (t) => { const { lockableFt, ali } = t.context.accounts; await lockableFt.call(lockableFt, "setAllowance", { escrowAccountId: ali.accountId, allowance: 100, }); const aliAllowance = await lockableFt.view("getAllowance", { ownerId: lockableFt.accountId, escrowAccountId: ali.accountId, }); t.is(aliAllowance, 100); const contractAllowance = await lockableFt.view("getAllowance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(contractAllowance, 0); }); test("Fail to set allowance for oneself", async (t) => { const { lockableFt } = t.context.accounts; const error = await t.throwsAsync(() => lockableFt.call(lockableFt, "setAllowance", { escrowAccountId: lockableFt.accountId, allowance: 100, }) ); t.assert(error.message.includes(`Can't set allowance for yourself`)); }); test("Lock owner", async (t) => { const { lockableFt } = t.context.accounts; await lockableFt.call(lockableFt, "lock", { ownerId: lockableFt.accountId, lockAmount: 100, }); const unlockedBalance = await lockableFt.view("getUnlockedBalance", { ownerId: lockableFt.accountId, }); t.is(unlockedBalance, 9900); const allowance = await lockableFt.view("getAllowance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(allowance, 0); const lockedBalance = await lockableFt.view("getLockedBalance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(lockedBalance, 100); }); test("Lock failures", async (t) => { const { lockableFt, ali } = t.context.accounts; const error1 = await t.throwsAsync(() => lockableFt.call(lockableFt, "lock", { ownerId: lockableFt.accountId, lockAmount: 0, }) ); t.assert(error1.message.includes(`Can't lock 0 or less tokens`)); const error2 = await t.throwsAsync(() => lockableFt.call(lockableFt, "lock", { ownerId: lockableFt.accountId, lockAmount: 10001, }) ); t.assert(error2.message.includes(`Not enough unlocked balance`)); const error3 = await t.throwsAsync(() => ali.call(lockableFt, "lock", { ownerId: lockableFt.accountId, lockAmount: 10, }) ); t.assert(error3.message.includes(`Not enough allowance`)); }); test("Unlock owner", async (t) => { const { lockableFt } = t.context.accounts; await lockableFt.call(lockableFt, "lock", { ownerId: lockableFt.accountId, lockAmount: 100, }); await lockableFt.call(lockableFt, "unlock", { ownerId: lockableFt.accountId, unlockAmount: 100, }); const unlockedBalance = await lockableFt.view("getUnlockedBalance", { ownerId: lockableFt.accountId, }); t.is(unlockedBalance, 10000); const allowance = await lockableFt.view("getAllowance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(allowance, 0); const lockedBalance = await lockableFt.view("getLockedBalance", { ownerId: lockableFt.accountId, escrowAccountId: lockableFt.accountId, }); t.is(lockedBalance, 0); }); test("Unlock failures", async (t) => { const { lockableFt } = t.context.accounts; const error1 = await t.throwsAsync(() => lockableFt.call(lockableFt, "unlock", { ownerId: lockableFt.accountId, unlockAmount: 0, }) ); t.assert(error1.message.includes(`Can't unlock 0 or less tokens`)); const error2 = await t.throwsAsync(() => lockableFt.call(lockableFt, "unlock", { ownerId: lockableFt.accountId, unlockAmount: 1, }) ); t.assert(error2.message.includes(`Not enough locked tokens`)); }); test("Simple transfer", async (t) => { const { lockableFt, ali } = t.context.accounts; await lockableFt.call(lockableFt, "transfer", { newOwnerId: ali.accountId, amount: 100, }); const ownerUnlockedBalance = await lockableFt.view("getUnlockedBalance", { ownerId: lockableFt.accountId, }); t.is(ownerUnlockedBalance, 9900); const aliUnlockedBalance = await lockableFt.view("getUnlockedBalance", { ownerId: ali.accountId, }); t.is(aliUnlockedBalance, 100); }); test("Transfer failures", async (t) => { const { lockableFt, ali } = t.context.accounts; const error1 = await t.throwsAsync(() => lockableFt.call(lockableFt, "transfer", { newOwnerId: ali.accountId, amount: 0, }) ); t.assert(error1.message.includes(`Can't transfer 0 or less tokens`)); const error2 = await t.throwsAsync(() => lockableFt.call(lockableFt, "transfer", { newOwnerId: ali.accountId, amount: 10001, }) ); t.assert(error2.message.includes(`Not enough unlocked balance`)); }); ================================================ FILE: examples/__tests__/test-fungible-token.ava.js ================================================ import { Worker, NEAR } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const totalSupply = 1000; const yoctoAccountStorage = "90"; const root = worker.rootAccount; const xcc = await root.devDeploy("./build/fungible-token-helper.wasm"); const ft = await root.createSubAccount("ft"); await ft.deploy("./build/fungible-token.wasm"); await root.call(ft, "init", { owner_id: root.accountId, total_supply: totalSupply.toString(), }); const alice = await root.createSubAccount("alice", { initialBalance: NEAR.parse("10 N").toJSON(), }); t.context.worker = worker; t.context.accounts = { root, ft, alice, xcc }; t.context.variables = { totalSupply, yoctoAccountStorage }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("should register account and pay for storage", async (t) => { const { ft, alice } = t.context.accounts; const { yoctoAccountStorage } = t.context.variables; const result = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); const aliceAfterBalance = await alice.balance(); const expected = { message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`, }; t.deepEqual(result, expected); t.true( aliceAfterBalance.total > NEAR.parse("9 N").toJSON(), "alice should have received a refund" ); }); test("should return message when account is already registered and not refund when no deposit is attached", async (t) => { const { ft, alice } = t.context.accounts; const { yoctoAccountStorage } = t.context.variables; const result = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); const expected = { message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`, }; t.deepEqual(result, expected); const result2 = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("0 N").toJSON() } ); t.is(result2.message, "Account is already registered"); }); test("should return message and refund predecessor caller when trying to pay for storage for an account that is already registered", async (t) => { const { ft, alice } = t.context.accounts; const { yoctoAccountStorage } = t.context.variables; const result = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); const expected = { message: `Account ${alice.accountId} registered with storage deposit of ${yoctoAccountStorage}`, }; t.deepEqual(result, expected); const result2 = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); t.is( result2.message, "Account is already registered, deposit refunded to predecessor" ); const aliceBalance = await alice.balance(); t.is( aliceBalance.total > NEAR.parse("9 N"), true, "alice should have received a refund" ); }); test("should return message when trying to pay for storage with less than the required amount and refund predecessor caller", async (t) => { const { ft, alice } = t.context.accounts; const { yoctoAccountStorage } = t.context.variables; const result = await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.from("40").toJSON() } ); t.is( result.message, `Not enough attached deposit to cover storage cost. Required: ${yoctoAccountStorage}` ); }); test("should throw when trying to transfer for an unregistered account", async (t) => { const { ft, alice, root } = t.context.accounts; try { await root.call( ft, "ft_transfer", { receiver_id: alice.accountId, amount: "1" }, { attachedDeposit: NEAR.from("1").toJSON() } ); } catch (error) { t.true( error.message.includes(`Account ${alice.accountId} is not registered`) ); } }); test("Owner has all balance in the beginning", async (t) => { const { ft, root } = t.context.accounts; const result = await ft.view("ft_balance_of", { account_id: root.accountId }); t.is(result, "1000"); }); test("Can transfer if balance is sufficient", async (t) => { const { alice, ft, root } = t.context.accounts; await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); await root.call( ft, "ft_transfer", { receiver_id: alice.accountId, amount: "100" }, { attachedDeposit: NEAR.from("1").toJSON() } ); const aliBalance = await ft.view("ft_balance_of", { account_id: alice.accountId, }); t.is(aliBalance, "100"); const ownerBalance = await ft.view("ft_balance_of", { account_id: root.accountId, }); t.is(ownerBalance, "900"); }); test("Cannot transfer if balance is not sufficient", async (t) => { const { alice, root, ft } = t.context.accounts; await alice.call( ft, "storage_deposit", { account_id: alice.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); try { await alice.call( ft, "ft_transfer", { receiverId: root.accountId, amount: "100", }, { attachedDeposit: NEAR.from("1").toJSON() } ); } catch (e) { t.assert( e .toString() .indexOf( "Smart contract panicked: assertion failed: The account doesn't have enough balance" ) >= 0 ); } }); test("Cross contract transfer", async (t) => { const { xcc, ft, root } = t.context.accounts; await xcc.call( ft, "storage_deposit", { account_id: xcc.accountId }, { attachedDeposit: NEAR.parse("1 N").toJSON() } ); await root.call( ft, "ft_transfer_call", { receiver_id: xcc.accountId, amount: "900", memo: null, msg: "test msg" }, { gas: 200000000000000, attachedDeposit: NEAR.from("1").toJSON() } ); const xccBalance = await ft.view("ft_balance_of", { account_id: xcc.accountId, }); t.is(xccBalance, "900"); const aliSubContractData = await xcc.view("get_contract_data"); t.is( aliSubContractData, `[900 from ${root.accountId} to ${xcc.accountId}] test msg ` ); const ownerBalance = await ft.view("ft_balance_of", { account_id: root.accountId, }); t.is(ownerBalance, "100"); }); ================================================ FILE: examples/__tests__/test-nested-collections.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const nestedCollections = await root.devDeploy( "./build/nested-collections.wasm" ); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, nestedCollections, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Ali sets then gets text", async (t) => { const { ali, nestedCollections } = t.context.accounts; await ali.call(nestedCollections, "add", { id: "1", text: "hello" }); await ali.call(nestedCollections, "add", { id: "2", text: "world" }); t.is( await nestedCollections.view("get", { id: "1", accountId: ali.accountId }), "hello" ); t.is( await nestedCollections.view("get", { id: "2", accountId: ali.accountId }), "world" ); }); test("Bob and Carl have different statuses", async (t) => { const { nestedCollections, bob, carl } = t.context.accounts; await bob.call(nestedCollections, "add", { id: "1", text: "hello" }); await carl.call(nestedCollections, "add", { id: "1", text: "world" }); t.is( await nestedCollections.view("get", { id: "1", accountId: bob.accountId }), "hello" ); t.is( await nestedCollections.view("get", { id: "1", accountId: carl.accountId }), "world" ); }); test("sets then gets nested nested collection", async (t) => { const { ali, bob, nestedCollections } = t.context.accounts; await ali.call(nestedCollections, "add_to_group", { group: "x", id: "1", text: "hello", }); await ali.call(nestedCollections, "add_to_group", { group: "x", id: "2", text: "world", }); await ali.call(nestedCollections, "add_to_group", { group: "y", id: "2", text: "cat", }); await bob.call(nestedCollections, "add_to_group", { group: "y", id: "2", text: "dog", }); t.is( await nestedCollections.view("get_from_group", { group: "x", id: "1", accountId: ali.accountId, }), "hello" ); t.is( await nestedCollections.view("get_from_group", { group: "x", id: "2", accountId: ali.accountId, }), "world" ); t.is( await nestedCollections.view("get_from_group", { group: "y", id: "2", accountId: ali.accountId, }), "cat" ); t.is( await nestedCollections.view("get_from_group", { group: "y", id: "2", accountId: bob.accountId, }), "dog" ); }); ================================================ FILE: examples/__tests__/test-non-fungible-token.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the nft contract. const nft = await root.devDeploy("./build/non-fungible-token.wasm"); // Init the contract await nft.call(nft, "init", { owner_id: nft.accountId, owner_by_id_prefix: "a", }); // Deploy the tokenReceiver contract. const tokenReceiver = await root.devDeploy( "./build/non-fungible-token-receiver.wasm" ); // Init the contract await tokenReceiver.call(tokenReceiver, "init", { nonFungibleTokenAccountId: nft.accountId, }); // Mint an NFT let tokenId = "my-cool-nft"; await nft.call(nft, "nftMint", { token_id: tokenId, token_owner_id: nft.accountId, }); // Create test accounts const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs, it is unique for each test t.context.worker = worker; t.context.accounts = { root, nft, tokenReceiver, tokenId, ali, bob }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed tear down the worker:", error); }); }); test("Owner has the NFT in the beginning", async (t) => { const { nft, tokenId } = t.context.accounts; const result = await nft.view("nftToken", { token_id: tokenId }); t.deepEqual(result, { owner_id: nft.accountId, token_id: tokenId }); }); test("Simple transfer", async (t) => { const { nft, tokenId, ali } = t.context.accounts; await nft.call(nft, "nftTransfer", { receiver_id: ali.accountId, token_id: tokenId, }); const result = await nft.view("nftToken", { token_id: tokenId }); t.deepEqual(result, { owner_id: ali.accountId, token_id: tokenId }); }); test("Transfer failures", async (t) => { const { nft, tokenId, ali } = t.context.accounts; const error1 = await t.throwsAsync(() => ali.call(nft, "nftTransfer", { receiver_id: nft.accountId, token_id: "non-existent-id", }) ); t.assert(error1.message.includes(`Token not found`)); const error2 = await t.throwsAsync(() => ali.call(nft, "nftTransfer", { receiver_id: nft.accountId, token_id: tokenId, }) ); t.assert(error2.message.includes(`Sender must be the current owner`)); const error3 = await t.throwsAsync(() => nft.call(nft, "nftTransfer", { receiver_id: nft.accountId, token_id: tokenId, }) ); t.assert(error3.message.includes(`Current and next owner must differ`)); }); test("Transfer call where receiver returns the token", async (t) => { const { nft, tokenReceiver, tokenId } = t.context.accounts; await nft.call( nft, "nftTransferCall", { receiver_id: tokenReceiver.accountId, token_id: tokenId, approval_id: null, memo: null, msg: "return-it-now", }, { gas: "120000000000000" } ); const result = await nft.view("nftToken", { token_id: tokenId }); t.deepEqual(result, { owner_id: nft.accountId, token_id: tokenId }); }); test("Transfer call where receiver keeps the token", async (t) => { const { nft, tokenReceiver, tokenId } = t.context.accounts; await nft.call( nft, "nftTransferCall", { receiver_id: tokenReceiver.accountId, token_id: tokenId, approval_id: null, memo: null, msg: "keep-it-now", }, { gas: "120000000000000" } ); const result = await nft.view("nftToken", { token_id: tokenId }); t.deepEqual(result, { owner_id: tokenReceiver.accountId, token_id: tokenId }); }); ================================================ FILE: examples/__tests__/test-parking-lot.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const parkingLot = await root.devDeploy("build/parking-lot.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, parkingLot, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("No BMW in the beginning", async (t) => { const { parkingLot } = t.context.accounts; t.is(await parkingLot.view("getCarSpecs", { name: "BMW" }), null); }); test("Can run the car after adding it", async (t) => { const { ali, parkingLot } = t.context.accounts; const bmwSpecs = { id: 1, color: "Black", price: 100500, engineHp: 500, }; await ali.call(parkingLot, "addCar", { name: "BMW", ...bmwSpecs, }); t.is(await parkingLot.view("runCar", { name: "BMW" }), "boom"); }); ================================================ FILE: examples/__tests__/test-programmatic-update.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; import * as fs from "fs"; import * as path from "path"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const ali = await root.createSubAccount("ali"); const contract = await root.devDeploy( "build/programmatic-update-before.wasm" ); await contract.call(contract, "init", { manager: ali.accountId }); t.context.worker = worker; t.context.accounts = { root, contract, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("the contract can be programmatically updated", async (t) => { const { ali, contract } = t.context.accounts; // ASSERT BEFORE CODE UPDATE const codeBefore = await contract.viewCodeRaw(); const beforeDefaultGreeting = await contract.view("get_greeting", {}); t.is(beforeDefaultGreeting, "Hello"); // ACT (UPDATE CODE) const code = fs.readFileSync( path.resolve("./build/programmatic-update-after.wasm") ); await ali.call(contract, "updateContract", code, { gas: "300" + "0".repeat(12), // 300 Tgas }); // ASSERT AFTER CODE UPDATE const codeAfter = await contract.viewCodeRaw(); const afterDefaultGreeting = await contract.view("view_greeting", {}); t.not(codeBefore, codeAfter, "code should be different after update"); t.is(afterDefaultGreeting, "Hi"); }); ================================================ FILE: examples/__tests__/test-state-message-migration-add-filed.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const contract = await root.devDeploy("./build/status-message.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, contract, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("migration works", async (t) => { const { contract, ali } = t.context.accounts; await ali.call(contract, "set_status", { message: "hello" }); t.is( await contract.view("get_status", { account_id: ali.accountId }), "hello" ); await contract.deploy("./build/status-message-migrate-add-field.wasm"); await ali.call(contract, "migrateState", {}); t.is( await contract.view("get_status", { account_id: ali.accountId }), "hello" ); await ali.call(contract, "set_new_status", { message: "hello" }); t.is( await contract.view("get_new_status", { account_id: ali.accountId }), "hello" ); }); ================================================ FILE: examples/__tests__/test-state-migration.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const contract = await root.devDeploy("./build/state-migration-original.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, contract, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("migration works", async (t) => { const { contract, ali } = t.context.accounts; await ali.call(contract, "addMessage", { message: { sender: "ali", header: "h1", text: "hello" } }); await ali.call(contract, "addMessage", { message: { sender: "ali", header: "h2", text: "world" } }); await ali.call(contract, "addMessage", { message: { sender: "ali", header: "h3", text: "This message is too log for new standard" } }); await ali.call(contract, "addMessage", { message: { sender: "ali", header: "h4", text: "!" } }); const res1 = await contract.view("countMessages", {}); t.is(res1, 4); await contract.deploy("./build/state-migration-new.wasm"); await ali.call(contract, "migrateState", {}); const res2 = await contract.view("countMessages", {}); t.is(res2, 3); }); ================================================ FILE: examples/__tests__/test-status-deserialize-class.ava.js ================================================ import {Worker} from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-deserialize-class.wasm"); await root.call(statusMessage, "init_contract", {}); const result = await statusMessage.view("is_contract_inited", {}); t.is(result, true); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_record", { message: "hello" }); t.is( await statusMessage.view("get_record", { account_id: ali.accountId }), "hello" ); }); test("Ali set_truck_info and get_truck_info", async (t) => { const { ali, statusMessage } = t.context.accounts; let carName = "Mercedes-Benz"; let speed = 240; await ali.call(statusMessage, "set_truck_info", { name: carName, speed: speed }); await ali.call(statusMessage, "add_truck_load", { name: "alice", load: "a box" }); await ali.call(statusMessage, "add_truck_load", { name: "bob", load: "a packet" }); t.is( await statusMessage.view("get_truck_info", { }), carName + " run with speed " + speed + " with loads length: 2" ); t.is( await statusMessage.view("get_user_car_info", { account_id: ali.accountId }), carName + " run with speed " + speed ); }); test("Ali push_message and get_messages", async (t) => { const { ali, statusMessage } = t.context.accounts; let message1 = 'Hello'; let message2 = 'World'; await ali.call(statusMessage, "push_message", { message: message1 }); await ali.call(statusMessage, "push_message", { message: message2 }); t.is( await statusMessage.view("get_messages", { }), 'Hello,World' ); }); test("Ali set_nested_efficient_recordes then get_nested_efficient_recordes text", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "1", message: "hello" }, { gas: 35_000_000_000_000n }); await bob.call(statusMessage, "set_nested_efficient_recordes", { id: "2", message: "world" }, { gas: 35_000_000_000_000n }); t.is( await statusMessage.view("get_efficient_recordes", { account_id: ali.accountId }), "hello" ); t.is( await statusMessage.view("get_nested_efficient_recordes", { id: "1", account_id: bob.accountId }), "hello" ); t.is( await statusMessage.view("get_nested_efficient_recordes", { id: "2", account_id: bob.accountId }), "world" ); t.is( await statusMessage.view("get_nested_lookup_recordes", { id: "1", account_id: bob.accountId }), "hello" ); t.is( await statusMessage.view("get_nested_lookup_recordes", { id: "2", account_id: bob.accountId }), "world" ); t.is( await statusMessage.view("get_vector_nested_group", { idx: 0, account_id: bob.accountId }), "world" ); t.is( await statusMessage.view("get_lookup_nested_vec", { account_id: bob.accountId, idx: 1 }), "world" ); t.is( await statusMessage.view("get_is_contains_user", { account_id: bob.accountId}), true ); }); test("Ali set_big_num_and_date then gets", async (t) => { const { ali, bob, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_big_num_and_date", { bigint_num: `${10n}`, new_date: new Date('August 19, 2023 23:15:30 GMT+00:00') }); const afterSetNum = await statusMessage.view("get_big_num", { }); t.is(afterSetNum, `${10n}`); const afterSetDate = await statusMessage.view("get_date", { }); t.is(afterSetDate.toString(), '2023-08-19T23:15:30.000Z'); }); test("Ali set_extra_data without schema defined then gets", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_extra_data", { message: "Hello world!", number: 100 }); const messageWithoutSchemaDefined = await statusMessage.view("get_extra_msg", { }); t.is(messageWithoutSchemaDefined, "Hello world!"); const numberWithoutSchemaDefined = await statusMessage.view("get_extra_number", { }); t.is(numberWithoutSchemaDefined, 100); }); test("Ali set_extra_record without schema defined then gets", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_extra_record", { message: "Hello world!"}); const recordWithoutSchemaDefined = await statusMessage.view("get_extra_record", { account_id: ali.accountId }); t.is(recordWithoutSchemaDefined, "Hello world!"); }); ================================================ FILE: examples/__tests__/test-status-message-borsh.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-message-borsh.wasm"); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Root gets null status", async (t) => { const { statusMessage, root } = t.context.accounts; const result = await statusMessage.view("get_status", { account_id: root.accountId, }); t.is(result, null); }); test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_status", { message: "hello" }); t.is( await statusMessage.view("get_status", { account_id: ali.accountId }), "hello" ); }); test("Bob and Carl have different statuses", async (t) => { const { statusMessage, bob, carl } = t.context.accounts; await bob.call(statusMessage, "set_status", { message: "hello" }); await carl.call(statusMessage, "set_status", { message: "world" }); const bobStatus = await statusMessage.view("get_status", { account_id: bob.accountId, }); const carlStatus = await statusMessage.view("get_status", { account_id: carl.accountId, }); t.is(bobStatus, "hello"); t.is(carlStatus, "world"); }); ================================================ FILE: examples/__tests__/test-status-message-collections.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the statis-message contract. const statusMessage = await root.devDeploy( "./build/status-message-collections.wasm" ); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Root gets null status", async (t) => { const { root, statusMessage } = t.context.accounts; const result = await statusMessage.view("get_status", { account_id: root.accountId, }); t.is(result, null); }); test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_status", { message: "hello" }); t.is( await statusMessage.view("get_status", { account_id: ali.accountId }), "hello" ); }); test("Bob and Carl have different statuses", async (t) => { const { statusMessage, bob, carl } = t.context.accounts; await bob.call(statusMessage, "set_status", { message: "hello" }); await carl.call(statusMessage, "set_status", { message: "world" }); const bobStatus = await statusMessage.view("get_status", { account_id: bob.accountId, }); const carlStatus = await statusMessage.view("get_status", { account_id: carl.accountId, }); t.is(bobStatus, "hello"); t.is(carlStatus, "world"); }); test("Get statuses from the contract", async (t) => { const { statusMessage, bob, carl } = t.context.accounts; await bob.call(statusMessage, "set_status", { message: "hello" }); await carl.call(statusMessage, "set_status", { message: "world" }); const statuses = await statusMessage.view("get_all_statuses", {}); t.deepEqual(statuses, [ [bob.accountId, "hello"], [carl.accountId, "world"], ]); }); test("message has stored by someone", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_status", { message: "hello" }); t.is(await statusMessage.view("has_status", { message: "hello" }), true); t.is(await statusMessage.view("has_status", { message: "world" }), false); }); ================================================ FILE: examples/__tests__/test-status-message-deserialize-err.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-message-deserialize-err.wasm"); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Root gets null status", async (t) => { const { statusMessage, root } = t.context.accounts; const result = await statusMessage.view("get_status", { account_id: root.accountId, }); t.is(result, null); }); test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_status", { message: "hello" }); let res = await ali.callRaw(statusMessage, "get_status", { account_id: ali.accountId }); t.assert( res.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Smart contract panicked: deserialize err" ) ); }); ================================================ FILE: examples/__tests__/test-status-message-serialize-err.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-message-serialize-err.wasm"); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Root gets null status", async (t) => { const { statusMessage, root } = t.context.accounts; const result = await statusMessage.view("get_status", { account_id: root.accountId, }); t.is(result, null); }); test("Ali sets status", async (t) => { const { ali, statusMessage } = t.context.accounts; let res = await ali.callRaw(statusMessage, "set_status", { message: "hello" }); t.assert( res.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Smart contract panicked: serialize err" ) ); }); ================================================ FILE: examples/__tests__/test-status-message.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const statusMessage = await root.devDeploy("./build/status-message.wasm"); // Create test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, statusMessage, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Root gets null status", async (t) => { const { statusMessage, root } = t.context.accounts; const result = await statusMessage.view("get_status", { account_id: root.accountId, }); t.is(result, null); }); test("Ali sets then gets status", async (t) => { const { ali, statusMessage } = t.context.accounts; await ali.call(statusMessage, "set_status", { message: "hello" }); t.is( await statusMessage.view("get_status", { account_id: ali.accountId }), "hello" ); }); test("Bob and Carl have different statuses", async (t) => { const { statusMessage, bob, carl } = t.context.accounts; await bob.call(statusMessage, "set_status", { message: "hello" }); await carl.call(statusMessage, "set_status", { message: "world" }); const bobStatus = await statusMessage.view("get_status", { account_id: bob.accountId, }); const carlStatus = await statusMessage.view("get_status", { account_id: carl.accountId, }); t.is(bobStatus, "hello"); t.is(carlStatus, "world"); }); ================================================ FILE: examples/ava.config.cjs ================================================ require("util").inspect.defaultOptions.depth = 5; // Increase AVA's printing depth module.exports = { timeout: "300000", files: ["**/*.ava.js"], failWithoutAssertions: false, extensions: ["js"], }; ================================================ FILE: examples/jsconfig.json ================================================ { "compilerOptions": { "experimentalDecorators": true }, "exclude": ["node_modules"], "files": ["src/counter.js"] } ================================================ FILE: examples/package.json ================================================ { "name": "examples", "version": "1.0.0", "description": "Status message example with near-sdk-js", "main": "index.js", "type": "module", "scripts": { "build": "run-s build:*", "build:status-message": "near-sdk-js build src/status-message/status-message.js build/status-message.wasm", "build:status-message-migrate-add-field": "near-sdk-js build src/status-message/status-message-migrate-add-field.js build/status-message-migrate-add-field.wasm", "build:status-message-borsh": "near-sdk-js build src/status-message/status-message-borsh.js build/status-message-borsh.wasm", "build:status-message-serialize-err": "near-sdk-js build src/status-message/status-message-serialize-err.js build/status-message-serialize-err.wasm", "build:status-message-deserialize-err": "near-sdk-js build src/status-message/status-message-deserialize-err.js build/status-message-deserialize-err.wasm", "build:clean-state": "near-sdk-js build src/clean-state.js build/clean-state.wasm", "build:counter": "near-sdk-js build src/counter/counter.js build/counter.wasm", "build:counter-lowlevel": "near-sdk-js build src/counter/counter-lowlevel.js build/counter-lowlevel.wasm", "build:counter-ts": "near-sdk-js build src/counter/counter.ts build/counter-ts.wasm", "build:cross-contract-call": "near-sdk-js build src/status-message/status-message.js build/status-message.wasm && near-sdk-js build src/cross-contract-calls/cross-contract-call.js build/cross-contract-call.wasm", "build:cross-contract-call-ts": "near-sdk-js build src/status-message/status-message.js build/status-message.wasm && near-sdk-js build src/cross-contract-calls/cross-contract-call.ts build/cross-contract-call-ts.wasm", "build:cross-contract-call-loop": "near-sdk-js build src/counter/counter.js build/counter.wasm && near-sdk-js build src/cross-contract-calls/cross-contract-call-loop.js build/cross-contract-call-loop.wasm", "build:fungible-token-lockable": "near-sdk-js build src/fungible-token/fungible-token-lockable.js build/fungible-token-lockable.wasm", "build:fungible-token": "near-sdk-js build src/fungible-token/fungible-token.ts build/fungible-token.wasm && near-sdk-js build src/fungible-token/fungible-token-helper.ts build/fungible-token-helper.wasm", "build:non-fungible-token": "near-sdk-js build src/non-fungible-token/non-fungible-token-receiver.js build/non-fungible-token-receiver.wasm && near-sdk-js build src/non-fungible-token/non-fungible-token.js build/non-fungible-token.wasm", "build:status-message-collections": "near-sdk-js build src/status-message/status-message-collections.js build/status-message-collections.wasm", "build:parking-lot": "near-sdk-js build src/parking-lot.ts build/parking-lot.wasm", "build:programmatic-updates": "near-sdk-js build src/programmatic-updates/programmatic-update-before.ts build/programmatic-update-before.wasm && near-sdk-js build src/programmatic-updates/programmatic-update-after.ts build/programmatic-update-after.wasm", "build:nested-collections": "near-sdk-js build src/nested-collections.ts build/nested-collections.wasm", "build-nft": "run-s build:nft-*", "build:nft-contract": "near-sdk-js build src/non-fungible-token/my-nft.ts build/my-nft.wasm", "build:nft-receiver": "near-sdk-js build src/non-fungible-token/test-token-receiver.ts build/nft-receiver.wasm", "build:nft-approval-receiver": "near-sdk-js build src/non-fungible-token/test-approval-receiver.ts build/nft-approval-receiver.wasm", "build:ft": "near-sdk-js build src/fungible-token/my-ft.ts build/my-ft.wasm", "build:state-migration": "run-s build:state-migration:*", "build:state-migration:original": "near-sdk-js build src/state-migration/state-migration-original.ts build/state-migration-original.wasm", "build:state-migration:new": "near-sdk-js build src/state-migration/state-migration-new.ts build/state-migration-new.wasm", "build:status-deserialize-class": "near-sdk-js build src/status-deserialize-class.js build/status-deserialize-class.wasm", "build:basic-updates-base": "near-sdk-js build src/basic-updates/basic-updates-base.js build/basic-updates-base.wasm", "build:basic-updates-update": "near-sdk-js build src/basic-updates/basic-updates-update.js build/basic-updates-update.wasm", "test": "ava && pnpm test:counter-lowlevel && pnpm test:counter-ts", "test:nft": "ava __tests__/standard-nft/*", "test:ft": "ava __tests__/standard-ft/*", "test:status-message": "ava __tests__/test-status-message.ava.js", "test:status-message-migrate-add-field": "ava __tests__/test-state-message-migration-add-filed.ava.js", "test:clean-state": "ava __tests__/test-clean-state.ava.js", "test:counter": "ava __tests__/test-counter.ava.js", "test:counter-lowlevel": "COUNTER_LOWLEVEL=1 ava __tests__/test-counter.ava.js", "test:counter-ts": "COUNTER_TS=1 ava __tests__/test-counter.ava.js", "test:cross-contract-call": "ava __tests__/test-cross-contract-call.ava.js", "test:cross-contract-call-ts": "ava __tests__/test-cross-contract-call-ts.ava.js", "test:cross-contract-call-loop": "ava __tests__/test-cross-contract-call-loop.ava.js", "test:fungible-token-lockable": "ava __tests__/test-fungible-token-lockable.ava.js", "test:fungible-token": "ava __tests__/test-fungible-token.ava.js", "test:non-fungible-token": "ava __tests__/test-non-fungible-token.ava.js", "test:status-message-collections": "ava __tests__/test-status-message-collections.ava.js", "test:parking-lot": "ava __tests__/test-parking-lot.ava.js", "test:programmatic-update": "ava __tests__/test-programmatic-update.ava.js", "test:state-migration": "ava __tests__/test-state-migration.ava.js", "test:nested-collections": "ava __tests__/test-nested-collections.ava.js", "test:status-message-borsh": "ava __tests__/test-status-message-borsh.ava.js", "test:status-message-serialize-err": "ava __tests__/test-status-message-serialize-err.ava.js", "test:status-message-deserialize-err": "ava __tests__/test-status-message-deserialize-err.ava.js", "test:status-deserialize-class": "ava __tests__/test-status-deserialize-class.ava.js", "test:basic-updates": "ava __tests__/test-basic-updates.ava.js" }, "author": "Near Inc ", "license": "Apache-2.0", "dependencies": { "lodash-es": "4.17.21", "near-contract-standards": "workspace:*", "near-sdk-js": "workspace:*", "typescript": "4.7.4", "borsh": "1.0.0" }, "devDependencies": { "@types/lodash-es": "4.17.12", "ava": "4.3.3", "near-workspaces": "4.0.0", "npm-run-all": "4.1.5" } } ================================================ FILE: examples/src/basic-updates/basic-updates-base.js ================================================ import { NearBindgen, call, view, near, Vector, ONE_NEAR } from "near-sdk-js"; const POINT_ONE = ONE_NEAR / 10000n; class PostedMessage { constructor() { this.premium = false; this.sender = ""; this.text = ""; } static new(premium, sender, text) { let posted_message = new PostedMessage(); posted_message.premium = premium; posted_message.sender = sender; posted_message.text = text; return posted_message; } } @NearBindgen({}) export class GuestBook { constructor() { this.messages = new Vector("a"); this.payments = new Vector("b"); } @call({payableFunction: true}) add_message({text}) { const payment = near.attachedDeposit(); let premium = payment > POINT_ONE; const sender = near.predecessorAccountId(); let message = PostedMessage.new(premium, sender, text); this.messages.push(message); this.payments.push(payment); } @view({}) get_message({ index }) { return this.messages.get(index) || null; } @view({}) get_payment({ index }) { return this.payments.get(index) || null; } } ================================================ FILE: examples/src/basic-updates/basic-updates-update.js ================================================ import {NearBindgen, call, view, near, migrate, Vector, assert, ONE_NEAR} from "near-sdk-js"; const POINT_ONE = ONE_NEAR / 10000n; class OldPostedMessage { constructor() { this.premium = false; this.sender = ""; this.text = ""; } } @NearBindgen({}) export class OldState { constructor() { this.messages = new Vector("a"); this.payments = new Vector("b"); } } class PostedMessage { constructor() { this.payment = 0n; this.premium = false; this.sender = ""; this.text = ""; } static new(payment, premium, sender, text) { let posted_message = new PostedMessage(); posted_message.payment = payment; posted_message.premium = premium; posted_message.sender = sender; posted_message.text = text; return posted_message; } } @NearBindgen({}) export class GuestBook { constructor() { this.messages = new Vector("a"); } @migrate({}) migrateState() { assert(this.messages !== undefined, "Contract state should not be deserialized in @migrate"); // retrieve the current state from the contract const _state = OldState._getState(); const _contract = OldState._create(); if (_state) { OldState._reconstruct(_contract, _state); } let new_messages = new Vector("p"); _contract.messages.toArray().forEach((posted, idx) => { let payment = _contract .payments .get(idx) || 0n; new_messages.push(PostedMessage.new(payment, posted.premium, posted.sender, posted.text)); }); _contract.messages.clear(); _contract.payments.clear(); this.messages = new_messages; } @call({payableFunction: true}) add_message({text}) { const payment = near.attachedDeposit(); let premium = payment > POINT_ONE; const sender = near.predecessorAccountId(); let message = PostedMessage.new(payment, premium, sender, text); this.messages.push(message); } @view({}) get_message({ index }) { return this.messages.get(index) || null; } } ================================================ FILE: examples/src/clean-state.js ================================================ import { NearBindgen, call, view, near } from "near-sdk-js"; @NearBindgen({}) export class CleanState { @call({}) clean({ keys }) { keys.forEach((key) => near.storageRemove(key)); } @call({}) put({ key, value }) { near.storageWrite(key, value); } @view({}) get({ key }) { return near.storageRead(key); } } ================================================ FILE: examples/src/counter/counter-lowlevel.js ================================================ // This contract implements exact same functionality as counter.js, but only use low level APIs import { near } from "near-sdk-js"; export function init() { let argsRaw = near.input(); let args = JSON.parse(argsRaw || "{}"); let initial = args.initial || 0; let count = initial; let state = JSON.stringify({ count }); near.storageWrite("STATE", state); } function deserialize() { let state = near.storageRead("STATE"); if (state) { return JSON.parse(state); } else { return { count: 0 }; } } export function getCount() { let state = deserialize(); let count = state.count; near.valueReturn(JSON.stringify(count)); } export function increase() { let argsRaw = near.input(); let args = JSON.parse(argsRaw || "{}"); let n = args.n || 1; let state = deserialize(); state.count += n; near.log(`Counter increased to ${state.count}`); near.storageWrite("STATE", JSON.stringify(state)); } export function decrease() { let argsRaw = near.input(); let args = JSON.parse(argsRaw || "{}"); let n = args.n || 1; let state = deserialize(); state.count -= n; near.log(`Counter decreased to ${state.count}`); near.storageWrite("STATE", JSON.stringify(state)); } ================================================ FILE: examples/src/counter/counter.js ================================================ import { NearBindgen, near, call, view } from "near-sdk-js"; import { isUndefined } from "lodash-es"; @NearBindgen({}) export class Counter { constructor() { this.count = 0; } @call({}) increase({ n = 1 }) { this.count += n; near.log(`Counter increased to ${this.count}`); } @call({}) decrease({ n }) { // you can use default argument `n=1` too // this is to illustrate a npm dependency: lodash can be used if (isUndefined(n)) { this.count -= 1; } else { this.count -= n; } near.log(`Counter decreased to ${this.count}`); } @view({}) getCount() { return this.count; } } ================================================ FILE: examples/src/counter/counter.ts ================================================ import { NearBindgen, near, call, view } from "near-sdk-js"; import { isUndefined } from "lodash-es"; import { log } from "./log"; @NearBindgen({}) export class Counter { count = 0; @call({}) increase({ n = 1 }: { n: number }) { this.count += n; near.log(`Counter increased to ${this.count}`); } @call({}) decrease({ n }: { n: number }) { // you can use default argument `n=1` too // this is to illustrate a npm dependency: lodash can be used if (isUndefined(n)) { this.count -= 1; } else { this.count -= n; } // this is to illustrate import a local ts module log(`Counter decreased to ${this.count}`); } @view({}) getCount(): number { return this.count; } } ================================================ FILE: examples/src/counter/log.ts ================================================ import { near } from "near-sdk-js"; export function log(msg: unknown) { near.log(msg); } ================================================ FILE: examples/src/cross-contract-calls/cross-contract-call-loop.js ================================================ import { call, near, NearBindgen, NearPromise, view } from "near-sdk-js"; const CONTRACTS = [ "first-contract.test.near", "second-contract.test.near", "third-contract.test.near", ]; const NO_ARGS = ""; const THIRTY_TGAS = BigInt("30" + "0".repeat(12)); @NearBindgen({}) export class LoopXCC { constructor() { this.count = 0; } @call({}) incrementCount() { let promise = NearPromise.new(CONTRACTS[0]).functionCall( "getCount", NO_ARGS, BigInt(0), THIRTY_TGAS ); for (let i = 1; i < CONTRACTS.length; i++) { promise = promise.and( NearPromise.new(CONTRACTS[i]).functionCall( "getCount", NO_ARGS, BigInt(0), THIRTY_TGAS ) ); } promise = promise.then( NearPromise.new(near.currentAccountId()).functionCall( "_incrementCountCallback", NO_ARGS, BigInt(0), THIRTY_TGAS ) ); return promise.asReturn(); } @call({ privateFunction: true }) _incrementCountCallback() { const callCount = near.promiseResultsCount(); for (let i = 0; i < callCount; i++) { const promiseResult = near.promiseResult(i); const result = JSON.parse(promiseResult); this.count += result; } return this.count; } @view({}) getCount() { return this.count; } } ================================================ FILE: examples/src/cross-contract-calls/cross-contract-call.js ================================================ import { NearBindgen, call, view, initialize, near } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class OnCall { constructor() { this.personOnCall = ""; this.statusMessageContract = ""; } @initialize({}) init({ statusMessageContract }) { this.personOnCall = "undefined"; this.statusMessageContract = statusMessageContract; } @call({}) set_person_on_call({ accountId }) { near.log(`Trying to set ${accountId} on-call`); const promise = near.promiseBatchCreate(this.statusMessageContract); near.promiseBatchActionFunctionCall( promise, "get_status", JSON.stringify({ account_id: accountId }), 0, 30000000000000 ); near.promiseThen( promise, near.currentAccountId(), "_set_person_on_call_private", JSON.stringify({ accountId: accountId }), 0, 30000000000000 ); } @call({ privateFunction: true }) _set_person_on_call_private({ accountId }) { near.log(`_set_person_on_call_private called, accountId ${accountId}`); const status = JSON.parse(near.promiseResult(0)); near.log(`${accountId} status is ${status}`); if (status === "AVAILABLE") { this.personOnCall = accountId; near.log(`${accountId} set on-call`); } else { near.log(`${accountId} can not be set on-call`); } } @view({}) person_on_call() { near.log(`Returning person on-call: ${this.personOnCall}`); return this.personOnCall; } } ================================================ FILE: examples/src/cross-contract-calls/cross-contract-call.ts ================================================ import { NearBindgen, call, view, initialize, near } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class OnCall { personOnCall:string =""; statusMessageContract:string =""; @initialize({}) init({ statusMessageContract }) { this.statusMessageContract = statusMessageContract; } @call({}) set_person_on_call({ accountId }) { near.log(`Trying to set ${accountId} on-call`); const promise = near.promiseBatchCreate(this.statusMessageContract); near.promiseBatchActionFunctionCall( promise, "get_status", JSON.stringify({ account_id: accountId }), 0, 30000000000000 ); near.promiseThen( promise, near.currentAccountId(), "_set_person_on_call_private", JSON.stringify({ accountId: accountId }), 0, 30000000000000 ); } @call({ privateFunction: true }) _set_person_on_call_private({ accountId }) { near.log(`_set_person_on_call_private called, accountId ${accountId}`); const status = JSON.parse(near.promiseResult(0)); near.log(`${accountId} status is ${status}`); if (status === "AVAILABLE") { this.personOnCall = accountId; near.log(`${accountId} set on-call`); } else { near.log(`${accountId} can not be set on-call`); } } @view({}) person_on_call() { near.log(`Returning person on-call: ${this.personOnCall}`); return this.personOnCall; } } ================================================ FILE: examples/src/fungible-token/fungible-token-helper.ts ================================================ import { NearBindgen, call, view } from "near-sdk-js"; @NearBindgen({}) class _FungibleTokenHelper { data = ""; @call({}) ft_on_transfer({ sender_id, amount, msg, receiver_id, }: { sender_id: string; amount: string; msg: string; receiver_id: string; }) { const concatString = `[${amount} from ${sender_id} to ${receiver_id}] ${msg} `; this.data = this.data.concat("", concatString); } @view({}) get_contract_data() { return this.data; } } ================================================ FILE: examples/src/fungible-token/fungible-token-lockable.js ================================================ import { NearBindgen, call, view, initialize, near, LookupMap, } from "near-sdk-js"; class Account { constructor(balance, allowances, lockedBalances) { this.balance = balance; // Current unlocked balance this.allowances = allowances; // Allowed account to the allowance amount this.lockedBalances = lockedBalances; // Allowed account to locked balance } setAllowance(escrowAccountId, allowance) { if (allowance > 0) { this.allowances[escrowAccountId] = allowance; } else if (allowance === 0) { delete this.allowances[escrowAccountId]; } else { throw Error("Allowance can't be negative"); } } getAllowance(escrowAccountId) { return this.allowances[escrowAccountId] || 0; } setLockedBalance(escrowAccountId, lockedBalance) { if (lockedBalance > 0) { this.lockedBalances[escrowAccountId] = lockedBalance; } else if (lockedBalance === 0) { delete this.lockedBalances[escrowAccountId]; } else { throw Error("Locked balance cannot be negative"); } } getLockedBalance(escrowAccountId) { return this.lockedBalances[escrowAccountId] || 0; } totalBalance() { let totalLockedBalance = Object.values(this.lockedBalances).reduce( (acc, val) => acc + val, 0 ); return this.balance + totalLockedBalance; } } @NearBindgen({ initRequired: true }) export class LockableFungibleToken { constructor() { this.accounts = new LookupMap("a"); // Account ID -> Account mapping this.totalSupply = 0; // Total supply of the all tokens } @initialize({}) init({ prefix, totalSupply }) { this.accounts = new LookupMap(prefix); this.totalSupply = totalSupply; let ownerId = near.signerAccountId(); let ownerAccount = this.getAccount(ownerId); ownerAccount.balance = this.totalSupply; this.setAccount(ownerId, ownerAccount); } getAccount(ownerId) { let account = this.accounts.get(ownerId); if (account === null) { return new Account(0, {}, {}); } return new Account( account.balance, account.allowances, account.lockedBalances ); } setAccount(accountId, account) { this.accounts.set(accountId, account); } @call({}) setAllowance({ escrowAccountId, allowance }) { let ownerId = near.predecessorAccountId(); if (escrowAccountId === ownerId) { throw Error("Can't set allowance for yourself"); } let account = this.getAccount(ownerId); let lockedBalance = account.getLockedBalance(escrowAccountId); if (lockedBalance > allowance) { throw Error( "The new allowance can't be less than the amount of locked tokens" ); } account.setAllowance(escrowAccountId, allowance - lockedBalance); this.setAccount(ownerId, account); } @call({}) lock({ ownerId, lockAmount }) { if (lockAmount <= 0) { throw Error("Can't lock 0 or less tokens"); } let escrowAccountId = near.predecessorAccountId(); let account = this.getAccount(ownerId); // Checking and updating unlocked balance if (account.balance < lockAmount) { throw Error("Not enough unlocked balance"); } account.balance -= lockAmount; // If locking by escrow, need to check and update the allowance. if (escrowAccountId !== ownerId) { let allowance = account.getAllowance(escrowAccountId); if (allowance < lockAmount) { throw Error("Not enough allowance"); } account.setAllowance(escrowAccountId, allowance - lockAmount); } // Updating total lock balance let lockedBalance = account.getLockedBalance(escrowAccountId); account.setLockedBalance(escrowAccountId, lockedBalance + lockAmount); this.setAccount(ownerId, account); } @call({}) unlock({ ownerId, unlockAmount }) { if (unlockAmount <= 0) { throw Error("Can't unlock 0 or less tokens"); } let escrowAccountId = near.predecessorAccountId(); let account = this.getAccount(ownerId); // Checking and updating locked balance let lockedBalance = account.getLockedBalance(escrowAccountId); if (lockedBalance < unlockAmount) { throw Error("Not enough locked tokens"); } account.setLockedBalance(escrowAccountId, lockedBalance - unlockAmount); // If unlocking by escrow, need to update allowance. if (escrowAccountId !== ownerId) { let allowance = account.getAllowance(escrowAccountId); account.setAllowance(escrowAccountId, allowance + unlockAmount); } // Updating unlocked balance account.balance += unlockAmount; this.setAccount(ownerId, account); } @call({}) transferFrom({ ownerId, newOwnerId, amount }) { if (amount <= 0) { throw Error("Can't transfer 0 or less tokens"); } let escrowAccountId = near.predecessorAccountId(); let account = this.getAccount(ownerId); // Checking and updating locked balance let lockedBalance = account.getLockedBalance(escrowAccountId); var remainingAmount; if (lockedBalance >= amount) { account.setLockedBalance(escrowAccountId, lockedBalance - amount); remainingAmount = 0; } else { account.setLockedBalance(escrowAccountId, 0); remainingAmount = amount - lockedBalance; } // If there is remaining balance after the locked balance, we try to use unlocked tokens. if (remainingAmount > 0) { // Checking and updating unlocked balance if (account.balance < remainingAmount) { throw Error("Not enough unlocked balance"); } account.balance -= remainingAmount; // If transferring by escrow, need to check and update allowance. if (escrowAccountId !== ownerId) { let allowance = account.getAllowance(escrowAccountId); // Checking and updating unlocked balance if (allowance < remainingAmount) { throw Error("Not enough allowance"); } account.setAllowance(escrowAccountId, allowance - remainingAmount); } } this.setAccount(ownerId, account); // Deposit amount to the new owner let newAccount = this.getAccount(newOwnerId); newAccount.balance += amount; this.setAccount(newOwnerId, newAccount); } @call({}) transfer({ newOwnerId, amount }) { this.transferFrom({ ownerId: near.predecessorAccountId(), newOwnerId, amount, }); } @view({}) getTotalSupply() { return this.totalSupply; } @view({}) getTotalBalance({ ownerId }) { return this.getAccount(ownerId).totalBalance(); } @view({}) getUnlockedBalance({ ownerId }) { return this.getAccount(ownerId).balance; } @view({}) getAllowance({ ownerId, escrowAccountId }) { return this.getAccount(ownerId).getAllowance(escrowAccountId); } @view({}) getLockedBalance({ ownerId, escrowAccountId }) { return this.getAccount(ownerId).getLockedBalance(escrowAccountId); } } ================================================ FILE: examples/src/fungible-token/fungible-token.ts ================================================ import { NearBindgen, call, view, initialize, near, LookupMap, assert, validateAccountId, } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class FungibleToken { accounts: LookupMap; accountRegistrants: LookupMap; accountDeposits: LookupMap; totalSupply: bigint; constructor() { this.accounts = new LookupMap("a"); this.accountRegistrants = new LookupMap("r"); this.accountDeposits = new LookupMap("d"); this.totalSupply = BigInt("0"); } @initialize({}) init({ owner_id, total_supply }: { owner_id: string; total_supply: string }) { Assertions.isLeftGreaterThanRight(total_supply, 0); validateAccountId(owner_id); this.totalSupply = BigInt(total_supply); this.accounts.set(owner_id, this.totalSupply); } internalGetAccountStorageUsage(accountLength: number): bigint { const initialStorageUsage = near.storageUsage(); const tempAccountId = "a".repeat(64); this.accounts.set(tempAccountId, BigInt("0")); const len64StorageUsage = near.storageUsage() - initialStorageUsage; const len1StorageUsage = len64StorageUsage / BigInt(64); const lenAccountStorageUsage = len1StorageUsage * BigInt(accountLength); this.accounts.remove(tempAccountId); return lenAccountStorageUsage * BigInt(3); // we create an entry in 3 maps } internalRegisterAccount({ registrantAccountId, accountId, amount, }: { registrantAccountId: string; accountId: string; amount: string; }) { assert( !this.accounts.containsKey(accountId), "Account is already registered" ); this.accounts.set(accountId, BigInt("0")); this.accountRegistrants.set(accountId, registrantAccountId); this.accountDeposits.set(accountId, BigInt(amount)); } internalSendNEAR(receivingAccountId: string, amount: bigint) { Assertions.isLeftGreaterThanRight(amount, 0); Assertions.isLeftGreaterThanRight( near.accountBalance(), amount, `Not enough balance ${near.accountBalance()} to send ${amount}` ); const promise = near.promiseBatchCreate(receivingAccountId); near.promiseBatchActionTransfer(promise, amount); near.promiseReturn(promise); } internalGetBalance(accountId: string): string { assert( this.accounts.containsKey(accountId), `Account ${accountId} is not registered` ); return this.accounts.get(accountId).toString(); } internalDeposit(accountId: string, amount: string) { const balance = this.internalGetBalance(accountId); const newBalance = BigInt(balance) + BigInt(amount); this.accounts.set(accountId, newBalance); const newSupply = BigInt(this.totalSupply) + BigInt(amount); this.totalSupply = newSupply; } internalWithdraw(accountId: string, amount: string) { const balance = this.internalGetBalance(accountId); const newBalance = BigInt(balance) - BigInt(amount); const newSupply = BigInt(this.totalSupply) - BigInt(amount); Assertions.isLeftGreaterThanRight( newBalance, -1, "The account doesn't have enough balance" ); Assertions.isLeftGreaterThanRight(newSupply, -1, "Total supply overflow"); this.accounts.set(accountId, newBalance); this.totalSupply = newSupply; } internalTransfer( senderId: string, receiverId: string, amount: string, _memo: string = null ) { assert(senderId != receiverId, "Sender and receiver should be different"); Assertions.isLeftGreaterThanRight(amount, 0); this.internalWithdraw(senderId, amount); this.internalDeposit(receiverId, amount); } @call({ payableFunction: true }) storage_deposit({ account_id }: { account_id: string }) { const accountId = account_id || near.predecessorAccountId(); validateAccountId(accountId); const attachedDeposit = near.attachedDeposit(); if (this.accounts.containsKey(accountId)) { if (attachedDeposit > 0) { this.internalSendNEAR(near.predecessorAccountId(), attachedDeposit); return { message: "Account is already registered, deposit refunded to predecessor", }; } return { message: "Account is already registered" }; } const storageCost = this.internalGetAccountStorageUsage(accountId.length); if (attachedDeposit < storageCost) { this.internalSendNEAR(near.predecessorAccountId(), attachedDeposit); return { message: `Not enough attached deposit to cover storage cost. Required: ${storageCost.toString()}`, }; } this.internalRegisterAccount({ registrantAccountId: near.predecessorAccountId(), accountId: accountId, amount: storageCost.toString(), }); const refund = attachedDeposit - storageCost; if (refund > 0) { near.log( "Storage registration refunding " + refund + " yoctoNEAR to " + near.predecessorAccountId() ); this.internalSendNEAR(near.predecessorAccountId(), refund); } return { message: `Account ${accountId} registered with storage deposit of ${storageCost.toString()}`, }; } @call({ payableFunction: true }) ft_transfer({ receiver_id, amount, memo, }: { receiver_id: string; amount: string; memo: string; }) { Assertions.hasAtLeastOneAttachedYocto(); const senderId = near.predecessorAccountId(); near.log( "Transfer " + amount + " token from " + senderId + " to " + receiver_id ); this.internalTransfer(senderId, receiver_id, amount, memo); } @call({ payableFunction: true }) ft_transfer_call({ receiver_id, amount, memo, msg, }: { receiver_id: string; amount: string; memo: string; msg: string; }) { Assertions.hasAtLeastOneAttachedYocto(); const senderId = near.predecessorAccountId(); this.internalTransfer(senderId, receiver_id, amount, memo); const promise = near.promiseBatchCreate(receiver_id); const params = { sender_id: senderId, amount: amount, msg: msg, receiver_id: receiver_id, }; near.log( "Transfer call " + amount + " token from " + senderId + " to " + receiver_id + " with message " + msg ); near.promiseBatchActionFunctionCall( promise, "ft_on_transfer", JSON.stringify(params), 0, 30000000000000 ); return near.promiseReturn(promise); } @view({}) ft_total_supply() { return this.totalSupply; } @view({}) ft_balance_of({ account_id }: { account_id: string }) { validateAccountId(account_id); return this.internalGetBalance(account_id); } } class Assertions { static hasAtLeastOneAttachedYocto() { assert( near.attachedDeposit() > BigInt(0), "Requires at least 1 yoctoNEAR to ensure signature" ); } static isLeftGreaterThanRight( left: string | bigint | number | boolean, right: string | bigint | number | boolean, message: string = null ) { const msg = message || `Provided amount ${left} should be greater than ${right}`; assert(BigInt(left) > BigInt(right), msg); } } ================================================ FILE: examples/src/fungible-token/my-ft.ts ================================================ import { StorageBalance, StorageBalanceBounds, StorageManagement, FungibleTokenCore, FungibleTokenResolver, FungibleToken, FungibleTokenMetadata, } from "near-contract-standards/lib"; //TODO: delete lib import { AccountId, Balance, PromiseOrValue, call, view, initialize, NearBindgen, IntoStorageKey, near, } from "near-sdk-js"; import { Option, } from "near-contract-standards/lib/non_fungible_token/utils"; // TODO: fix import class FTPrefix implements IntoStorageKey { into_storage_key(): string { return "A"; // TODO: What is the best value to put here? } } @NearBindgen({ requireInit: true }) export class MyFt implements FungibleTokenCore, StorageManagement, FungibleTokenResolver { token: FungibleToken; metadata: FungibleTokenMetadata; constructor() { this.token = new FungibleToken(); this.metadata = new FungibleTokenMetadata("", "", "", "", null, null, 0); } @initialize({}) init({ owner_id, total_supply, metadata, }: { owner_id: AccountId; total_supply: Balance; metadata: FungibleTokenMetadata; }) { metadata.assert_valid(); this.token = new FungibleToken().init(new FTPrefix()); this.metadata = metadata; this.token.internal_register_account(owner_id); this.token.internal_deposit(owner_id, total_supply); } @initialize({}) init_with_default_meta({ owner_id, total_supply }: { owner_id: AccountId; total_supply: Balance; }) { const metadata = new FungibleTokenMetadata( "ft-1.0.0", "Example NEAR fungible token", "EXAMPLE", "DATA_IMAGE_SVG_NEAR_ICON", null, null, 24, ); return this.init({ owner_id, total_supply, metadata }) } @call({}) measure_account_storage_usage() { return this.token.measure_account_storage_usage(); } @call({ payableFunction: true }) ft_transfer({ receiver_id, amount, memo }: { receiver_id: AccountId, amount: Balance, memo?: String }) { return this.token.ft_transfer({ receiver_id, amount, memo }); } @call({ payableFunction: true }) ft_transfer_call({ receiver_id, amount, memo, msg }: { receiver_id: AccountId, amount: Balance, memo: Option, msg: string }): PromiseOrValue { return this.token.ft_transfer_call({ receiver_id, amount, memo, msg }); } @view({}) ft_total_supply(): Balance { return this.token.ft_total_supply(); } @view({}) ft_balance_of({ account_id }: { account_id: AccountId }): Balance { return this.token.ft_balance_of({ account_id }); } @call({ payableFunction: true }) storage_deposit( { account_id, registration_only, }: { account_id?: AccountId, registration_only?: boolean, } ): StorageBalance { return this.token.storage_deposit({ account_id, registration_only }); } @view({}) storage_withdraw({ amount }: { amount?: bigint }): StorageBalance { return this.token.storage_withdraw({ amount }); } @call({ payableFunction: true }) storage_unregister({ force }: { force?: boolean }): boolean { return this.token.storage_unregister({ force }); } @view({}) storage_balance_bounds(): StorageBalanceBounds { return this.token.storage_balance_bounds(); } @view({}) storage_balance_of({ account_id }: { account_id: AccountId }): Option { return this.token.storage_balance_of({ account_id }); } @call({}) ft_resolve_transfer({ sender_id, receiver_id, amount }: { sender_id: AccountId, receiver_id: AccountId, amount: Balance }): Balance { return this.token.ft_resolve_transfer({ sender_id, receiver_id, amount }); } } ================================================ FILE: examples/src/nested-collections.ts ================================================ import { NearBindgen, near, call, view, UnorderedMap } from "near-sdk-js"; @NearBindgen({}) export class Contract { outerMap: UnorderedMap>; groups: UnorderedMap>>; constructor() { this.outerMap = new UnorderedMap("o"); this.groups = new UnorderedMap("gs"); } @call({}) add({ id, text }: { id: string; text: string }) { const innerMap = this.outerMap.get(id, { reconstructor: UnorderedMap.reconstruct, defaultValue: new UnorderedMap("i_" + id + "_"), }); innerMap.set(near.signerAccountId(), text); this.outerMap.set(id, innerMap); } @view({}) get({ id, accountId }: { id: string; accountId: string }) { const innerMap = this.outerMap.get(id, { reconstructor: UnorderedMap.reconstruct, }); if (innerMap === null) { return null; } return innerMap.get(accountId); } @call({}) add_to_group({ group, id, text, }: { group: string; id: string; text: string; }) { const groupMap = this.groups.get(group, { reconstructor: UnorderedMap.reconstruct, defaultValue: new UnorderedMap>("g_" + group + "_"), }); const innerMap = groupMap.get(id, { reconstructor: UnorderedMap.reconstruct, defaultValue: new UnorderedMap("gi_" + group + "_" + id + "_"), }); innerMap.set(near.signerAccountId(), text); groupMap.set(id, innerMap); this.groups.set(group, groupMap); } @view({}) get_from_group({ group, id, accountId, }: { group: string; id: string; accountId: string; }) { const groupMap = this.groups.get(group, { reconstructor: UnorderedMap.reconstruct, }); if (groupMap === null) { return null; } const innerMap = groupMap.get(id, { reconstructor: UnorderedMap.reconstruct, }); if (innerMap === null) { return null; } return innerMap.get(accountId); } } ================================================ FILE: examples/src/non-fungible-token/my-nft.ts ================================================ import { NonFungibleToken } from "near-contract-standards/lib"; import { assert, call, initialize, near, NearBindgen, NearPromise, PromiseOrValue, view, IntoStorageKey } from "near-sdk-js"; import { NFTContractMetadata, NonFungibleTokenMetadataProvider, TokenMetadata, } from "near-contract-standards/lib/non_fungible_token/metadata"; import { Option, } from "near-contract-standards/lib/non_fungible_token/utils"; import { AccountId } from "near-sdk-js"; import { NonFungibleTokenCore } from "near-contract-standards/lib/non_fungible_token/core"; import { Token, TokenId, } from "near-contract-standards/lib/non_fungible_token/token"; import { NonFungibleTokenResolver } from "near-contract-standards/lib/non_fungible_token/core/resolver"; import { NonFungibleTokenApproval } from "near-contract-standards/lib/non_fungible_token/approval"; import { NonFungibleTokenEnumeration } from "near-contract-standards/lib/non_fungible_token/enumeration"; class StorageKey { } class StorageKeyNonFungibleToken extends StorageKey implements IntoStorageKey { into_storage_key(): string { return "NFT_"; } } class StorageKeyTokenMetadata extends StorageKey implements IntoStorageKey { into_storage_key(): string { return "TOKEN_METADATA_"; } } class StorageKeyTokenEnumeration extends StorageKey implements IntoStorageKey { into_storage_key(): string { return "TOKEN_ENUMERATION_"; } } class StorageKeyApproval extends StorageKey implements IntoStorageKey { into_storage_key(): string { return "APPROVAL1_"; } } @NearBindgen({ requireInit: true }) export class MyNFT implements NonFungibleTokenCore, NonFungibleTokenMetadataProvider, NonFungibleTokenResolver, NonFungibleTokenApproval, NonFungibleTokenEnumeration { tokens: NonFungibleToken; metadata: Option; constructor() { this.tokens = new NonFungibleToken(); this.metadata = new NFTContractMetadata(); } @view({}) nft_total_supply(): number { return this.tokens.nft_total_supply(); } @view({}) nft_tokens({ from_index, limit, }: { from_index?: number; limit?: number; }): Token[] { return this.tokens.nft_tokens({ from_index, limit }); } @view({}) nft_supply_for_owner({ account_id }: { account_id: string }): number { return this.tokens.nft_supply_for_owner({ account_id }); } @view({}) nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: string; from_index?: number; limit?: number; }): Token[] { return this.tokens.nft_tokens_for_owner({ account_id, from_index, limit }); } @call({ payableFunction: true }) nft_approve({ token_id, account_id, msg, }: { token_id: string; account_id: string; msg?: string; }): Option { return this.tokens.nft_approve({ token_id, account_id, msg }); } @call({ payableFunction: true }) nft_revoke({ token_id, account_id, }: { token_id: string; account_id: string; }) { return this.tokens.nft_revoke({ token_id, account_id }); } @call({ payableFunction: true }) nft_revoke_all({ token_id }: { token_id: string }) { return this.tokens.nft_revoke_all({ token_id }); } @view({}) nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: string; approved_account_id: string; approval_id?: bigint; }): boolean { return this.tokens.nft_is_approved({ token_id, approved_account_id, approval_id, }); } @call({}) nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: string; receiver_id: string; token_id: string; approved_account_ids?: { [approval: string]: bigint }; }): boolean { return this.tokens.nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }); } @view({}) nft_metadata(): NFTContractMetadata { assert(this.metadata !== null, "Metadata not initialized"); return this.metadata; } @call({ payableFunction: true }) nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: string; token_id: string; approval_id?: bigint; memo?: string; }) { this.tokens.nft_transfer({ receiver_id, token_id, approval_id, memo }); } @call({ payableFunction: true }) nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }: { receiver_id: string; token_id: string; approval_id?: bigint; memo?: string; msg: string; }): PromiseOrValue { return this.tokens.nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }); } @view({}) nft_token({ token_id }: { token_id: string }): Option { return this.tokens.nft_token({ token_id }); } @initialize({ requireInit: true }) init({ owner_id, metadata, }: { owner_id: string; metadata: NFTContractMetadata; }) { this.metadata = Object.assign(new NFTContractMetadata(), metadata); this.metadata.assert_valid(); this.tokens = new NonFungibleToken(); this.tokens.init( new StorageKeyNonFungibleToken(), owner_id, new StorageKeyTokenMetadata(), new StorageKeyTokenEnumeration(), new StorageKeyApproval() ); } @call({ payableFunction: true }) nft_mint({ token_id, token_owner_id, token_metadata, }: { token_id: TokenId; token_owner_id: AccountId; token_metadata: TokenMetadata; }) { assert( near.predecessorAccountId() === this.tokens.owner_id, "Unauthorized" ); this.tokens.internal_mint(token_id, token_owner_id, token_metadata); } } ================================================ FILE: examples/src/non-fungible-token/non-fungible-token-receiver.js ================================================ import { NearBindgen, call, near, assert, initialize } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class NftContract { constructor() { this.nonFungibleTokenAccountId = ""; } @initialize({}) init({ nonFungibleTokenAccountId }) { this.nonFungibleTokenAccountId = nonFungibleTokenAccountId; } @call({}) nftOnTransfer({ senderId, previousOwnerId, tokenId, msg }) { near.log( `nftOnTransfer called, params: senderId: ${senderId}, previousOwnerId: ${previousOwnerId}, tokenId: ${tokenId}, msg: ${msg}` ); assert( near.predecessorAccountId() === this.nonFungibleTokenAccountId, "Only supports the one non-fungible token contract" ); if (msg === "return-it-now") { near.log(`Returning ${tokenId} to ${senderId}`); return false; } else if (msg === "keep-it-now") { near.log(`Keep ${tokenId}`); return true; } else { throw Error("unsupported msg"); } } } ================================================ FILE: examples/src/non-fungible-token/non-fungible-token.js ================================================ import { NearBindgen, call, view, initialize, near, LookupMap, assert, } from "near-sdk-js"; class Token { constructor(token_id, owner_id) { this.token_id = token_id; this.owner_id = owner_id; } } @NearBindgen({ requireInit: true }) export class NftContract { constructor() { this.owner_id = ""; this.owner_by_id = new LookupMap("a"); } @initialize({}) init({ owner_id, owner_by_id_prefix }) { this.owner_id = owner_id; this.owner_by_id = new LookupMap(owner_by_id_prefix); } internalTransfer({ sender_id, receiver_id, token_id, approval_id: _ai, memo: _m, }) { let owner_id = this.owner_by_id.get(token_id); assert(owner_id !== null, "Token not found"); assert(sender_id === owner_id, "Sender must be the current owner"); assert(owner_id !== receiver_id, "Current and next owner must differ"); this.owner_by_id.set(token_id, receiver_id); return owner_id; } @call({}) nftTransfer({ receiver_id, token_id, approval_id, memo }) { let sender_id = near.predecessorAccountId(); this.internalTransfer({ sender_id, receiver_id, token_id, approval_id, memo, }); } @call({}) nftTransferCall({ receiver_id, token_id, approval_id, memo, msg }) { near.log( `nftTransferCall called, receiver_id ${receiver_id}, token_id ${token_id}` ); let sender_id = near.predecessorAccountId(); let old_owner_id = this.internalTransfer({ sender_id, receiver_id, token_id, approval_id, memo, }); const promise = near.promiseBatchCreate(receiver_id); near.promiseBatchActionFunctionCall( promise, "nftOnTransfer", JSON.stringify({ senderId: sender_id, previousOwnerId: old_owner_id, tokenId: token_id, msg: msg, }), 0, 30000000000000 ); near.promiseThen( promise, near.currentAccountId(), "_nftResolveTransfer", JSON.stringify({ sender_id, receiver_id, token_id }), 0, 30000000000000 ); } @call({ privateFunction: true }) _nftResolveTransfer({ sender_id, receiver_id, token_id }) { near.log( `_nftResolveTransfer called, receiver_id ${receiver_id}, token_id ${token_id}` ); const isTokenTransfered = JSON.parse(near.promiseResult(0)); near.log( `${token_id} ${ isTokenTransfered ? "was transfered" : "was NOT transfered" }` ); if (!isTokenTransfered) { near.log(`Returning ${token_id} to ${receiver_id}`); const currentOwner = this.owner_by_id.get(token_id); if (currentOwner === receiver_id) { this.internalTransfer({ sender_id: receiver_id, receiver_id: sender_id, token_id: token_id, approval_id: null, memo: null, }); near.log(`${token_id} returned to ${sender_id}`); return; } near.log( `Failed to return ${token_id}. It was burned or not owned by ${receiver_id} now.` ); } } @call({}) nftMint({ token_id, token_owner_id, token_metadata: _ }) { let sender_id = near.predecessorAccountId(); assert(sender_id === this.owner_id, "Unauthorized"); assert(this.owner_by_id.get(token_id) === null, "Token ID must be unique"); this.owner_by_id.set(token_id, token_owner_id); return new Token(token_id, token_owner_id); } @view({}) nftToken({ token_id }) { let owner_id = this.owner_by_id.get(token_id); if (owner_id === null) { return null; } return new Token(token_id, owner_id); } } ================================================ FILE: examples/src/non-fungible-token/test-approval-receiver.ts ================================================ import { initialize, near, NearBindgen, NearPromise, PromiseOrValue, assert, call, serialize, } from "near-sdk-js"; import { AccountId } from "near-sdk-js"; import { NonFungibleTokenApprovalReceiver } from "near-contract-standards/lib/non_fungible_token/approval/approval_receiver"; const BASE_GAS = 20_000_000_000_000n; const PROMISE_CALL = 20_000_000_000_000n; const GAS_FOR_NFT_ON_APPROVE = BASE_GAS + PROMISE_CALL; interface ValueReturnInterface { ok_go({ msg }: { msg: string }): PromiseOrValue; } @NearBindgen({ requireInit: true }) export class ApprovalReceiver implements NonFungibleTokenApprovalReceiver, ValueReturnInterface { public non_fungible_token_account_id: AccountId; constructor() { this.non_fungible_token_account_id = ""; } @call({}) nft_on_approve({ token_id, owner_id, approval_id, msg, }: { token_id: string; owner_id: string; approval_id: bigint; msg: string; }): PromiseOrValue { assert( near.predecessorAccountId() === this.non_fungible_token_account_id, "Only supports the one non-fungible token contract" ); near.log( `in nft_on_approve; token_id=${token_id}, owner_id=${owner_id}, approval_id=${approval_id}, msg=${msg}` ); switch (msg) { case "return-now": return "cool"; default: { const prepaid_gas = near.prepaidGas(); const account_id = near.currentAccountId(); return NearPromise.new(account_id).functionCallRaw( "ok_go", serialize({ msg }), 0n, prepaid_gas - GAS_FOR_NFT_ON_APPROVE ); } } } @call({}) ok_go({ msg }: { msg: string }): PromiseOrValue { near.log(`in ok_go, msg=${msg}`); return msg; } @initialize({}) init({ non_fungible_token_account_id, }: { non_fungible_token_account_id: AccountId; }) { this.non_fungible_token_account_id = non_fungible_token_account_id; } } ================================================ FILE: examples/src/non-fungible-token/test-token-receiver.ts ================================================ import { NonFungibleTokenReceiver } from "near-contract-standards/lib/non_fungible_token/core/receiver"; import { assert, call, initialize, near, NearBindgen, NearPromise, PromiseOrValue, serialize, } from "near-sdk-js"; import { AccountId } from "near-sdk-js"; const BASE_GAS = 10_000_000_000_000n; const PROMISE_CALL = 10_000_000_000_000n; const GAS_FOR_NFT_ON_TRANSFER = BASE_GAS + PROMISE_CALL; interface ValueReturnInterface { ok_go({ return_it }: { return_it: boolean }): PromiseOrValue; } @NearBindgen({ requireInit: true }) export class TokenReceiver implements NonFungibleTokenReceiver, ValueReturnInterface { public non_fungible_token_account_id: AccountId; constructor() { this.non_fungible_token_account_id = ""; } @initialize({}) init({ non_fungible_token_account_id, }: { non_fungible_token_account_id: AccountId; }) { this.non_fungible_token_account_id = non_fungible_token_account_id; } @call({}) nft_on_transfer({ sender_id, previous_owner_id, token_id, msg, }: { sender_id: string; previous_owner_id: string; token_id: string; msg: string; }): PromiseOrValue { assert( near.predecessorAccountId() === this.non_fungible_token_account_id, "Only supports the one non-fungible token contract" ); near.log( `in nft_on_transfer; sender_id=${sender_id}, previous_owner_id=${previous_owner_id}, token_id=${token_id}, msg=${msg}` ); switch (msg) { case "return-it-now": return true; case "return-it-later": { const prepaid_gas = near.prepaidGas(); const account_id = near.currentAccountId(); return NearPromise.new(account_id).functionCallRaw( "ok_go", serialize({ return_it: true }), 0n, prepaid_gas - GAS_FOR_NFT_ON_TRANSFER ); } case "keep-it-now": return false; case "keep-it-later": { const prepaid_gas = near.prepaidGas(); const account_id = near.currentAccountId(); return NearPromise.new(account_id).functionCallRaw( "ok_go", serialize({ return_it: false }), 0n, prepaid_gas - GAS_FOR_NFT_ON_TRANSFER ); } default: throw new Error("unsupported msg"); } } @call({}) ok_go({ return_it }: { return_it: boolean }): PromiseOrValue { near.log(`in ok_go, return_it=${return_it}`); return return_it; } } ================================================ FILE: examples/src/parking-lot.ts ================================================ import { NearBindgen, near, call, view, LookupMap } from "near-sdk-js"; class CarSpecs { id: number; color: string; price: number; engine: Engine; constructor(id: number, color: string, price: number, engine: Engine) { this.id = id; this.color = color; this.price = price; this.engine = engine; } } class Engine { hp: number; constructor(hp: number) { this.hp = hp; } run(): string { if (this.hp > 400) { return "boom"; } else { return "zoom"; } } } @NearBindgen({}) export class ParkingLot { cars: LookupMap; constructor() { this.cars = new LookupMap("a"); } @call({}) addCar({ name, id, color, price, engineHp, }: { name: string; id: number; color: string; price: number; engineHp: number; }) { // args can be json arguments only, they cannot be of a JS/TS class like following, unless override NearContract.deserializeArgs method. // addCar({ name, specs }: { name: string, specs: CarSpecs }) { const engine = new Engine(engineHp); const car = new CarSpecs(id, color, price, engine); near.log(`addCar() called, name: ${name}, specs: ${JSON.stringify(car)}`); this.cars.set(name, car); } @call({}) removeCar({ name }: { name: string }) { near.log(`removeCar() called, name: ${name}`); this.cars.remove(name); } @view({}) getCarSpecs({ name }: { name: string }): CarSpecs { near.log(`getCarSpecs() called, name: ${name}`); return this.cars.get(name); } @view({}) runCar({ name }: { name: string }) { /* We are getting plain carSpecs object from the storage. It needs to be converted to the class object in order to execute engine.run() function.*/ const carSpecs = this.cars.get(name) as CarSpecs; const engine = new Engine(carSpecs.engine.hp); return engine.run(); } } ================================================ FILE: examples/src/programmatic-updates/programmatic-update-after.ts ================================================ import { NearBindgen, near, initialize, assert, view } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class ProgrammaticUpdateAfter { greeting = "Hello"; @initialize({ privateFunction: true }) init({ manager }: { manager: string }) { near.log(`Setting manager to be ${manager}`); near.storageWrite("MANAGER", manager); } @view({}) // Method renamed and return "Hi" when greeting is "Hello" view_greeting(): string { return this.greeting.replace("Hello", "Hi"); } } export function updateContract() { const manager = near.storageRead("MANAGER"); assert( near.predecessorAccountId() === manager, "Only the manager can update the code" ); const promiseId = near.promiseBatchCreate(near.currentAccountId()); near.promiseBatchActionDeployContract(promiseId, near.inputRaw()); return near.promiseReturn(promiseId); } ================================================ FILE: examples/src/programmatic-updates/programmatic-update-before.ts ================================================ import { NearBindgen, near, initialize, assert, view } from "near-sdk-js"; @NearBindgen({ requireInit: true }) export class ProgrammaticUpdateBefore { greeting = "Hello"; @initialize({ privateFunction: true }) init({ manager }: { manager: string }) { near.log(`Setting manager to be ${manager}`); near.storageWrite("MANAGER", manager); } @view({}) // This method will be renamed after update and will return "Hi" if greeting is "Hello" get_greeting(): string { return this.greeting; } } export function updateContract() { const manager = near.storageRead("MANAGER"); assert( near.predecessorAccountId() === manager, "Only the manager can update the code" ); const promiseId = near.promiseBatchCreate(near.currentAccountId()); near.promiseBatchActionDeployContract(promiseId, near.inputRaw()); return near.promiseReturn(promiseId); } ================================================ FILE: examples/src/state-migration/state-migration-new.ts ================================================ import { NearBindgen, view, near, migrate, call, Vector, assert } from 'near-sdk-js' import { AccountId } from 'near-sdk-js/lib/types' type OldMessageFormat = { sender: AccountId, header: string, text: string, } // This is the new version of the Message type, it has an additional field type NewMessageFormat = { sender: AccountId, recipient?: AccountId, header: string, text: string, } @NearBindgen({}) export class MigrationDemo { messages: Vector; constructor() { this.messages = new Vector('messages'); } @call({ payableFunction: true }) addMessage({ message }: { message: NewMessageFormat }): void { this.messages.push(message); near.log(`${near.signerAccountId()} added message ${JSON.stringify(message)}`); } @view({}) countMessages(): number { return this.messages.toArray().length; } @migrate({}) migrateState(): Vector { assert(this.messages.toArray().length == 0, "Contract state should not be deserialized in @migrate"); // retrieve the current state from the contract let raw_vector = JSON.parse(near.storageRead("STATE")).messages; let old_messages: Vector = new Vector(raw_vector.prefix, raw_vector.length); near.log("old_messages: " + JSON.stringify(old_messages)); // iterate through the state migrating it to the new version let new_messages: Vector = new Vector('messages'); for (let old_message of old_messages) { near.log(`migrating ${JSON.stringify(old_message)}`); const new_message: NewMessageFormat = { sender: old_message.sender, recipient: "Unknown", header: old_message.header, text: old_message.text, }; if (new_message.text.length < 10) { near.log(`adding ${new_message} to new_messages`); new_messages.push(new_message); } else { near.log(`${new_message} is too long, skipping`); } } this.messages = new_messages; return this.messages; } } ================================================ FILE: examples/src/state-migration/state-migration-original.ts ================================================ import { NearBindgen, view, near, call, Vector } from 'near-sdk-js' import { AccountId } from 'near-sdk-js/lib/types' type Message = { sender: AccountId, header: string, text: string, } @NearBindgen({}) export class MigrationDemo { messages: Vector; constructor() { this.messages = new Vector('messages'); } @call({ payableFunction: true }) addMessage({ message }: { message: Message }): void { this.messages.push(message); near.log(`${near.signerAccountId()} added message ${JSON.stringify(message)}`); } @view({}) countMessages(): number { return this.messages.toArray().length; } } ================================================ FILE: examples/src/status-deserialize-class.js ================================================ import { NearBindgen, call, view, near, UnorderedMap, LookupMap, Vector, UnorderedSet, } from "near-sdk-js"; class Car { static schema = { name: "string", speed: "number", }; constructor() { this.name = ""; this.speed = 0; } info() { return this.name + " run with speed " + this.speed.toString() } } class Truck { static schema = { name: "string", speed: "number", loads: UnorderedMap }; constructor() { this.name = ""; this.speed = 0; this.loads = new UnorderedMap("tra"); } info() { return this.name + " run with speed " + this.speed.toString() + " with loads length: " + this.loads.toArray().length; } } // sdk should first try if UnorderedMap has a static schema and use it to recursively decode. // In this case, UnorderedMap doesn't. // So sdk should next try call UnorderedMap.reconstruct. @NearBindgen({}) export class StatusDeserializeClass { static schema = { truck: Truck, efficient_recordes: UnorderedMap, nested_efficient_recordes: {class: UnorderedMap, value: UnorderedMap}, nested_lookup_recordes: {class: UnorderedMap, value: LookupMap}, vector_nested_group: {class: Vector, value: LookupMap}, lookup_nest_vec: { class: LookupMap, value: Vector }, unordered_set: UnorderedSet, user_car_map: {class: UnorderedMap, value: Car }, big_num: 'bigint', date: 'date' }; constructor() { this.is_inited = false; this.records = {}; this.truck = new Truck(); this.messages = []; // account_id -> message this.efficient_recordes = new UnorderedMap("a"); // id -> account_id -> message this.nested_efficient_recordes = new UnorderedMap("b"); // id -> account_id -> message this.nested_lookup_recordes = new UnorderedMap("c"); // index -> account_id -> message this.vector_nested_group = new Vector("d"); // account_id -> index -> message this.lookup_nest_vec = new LookupMap("e"); this.unordered_set = new UnorderedSet("f"); this.user_car_map = new UnorderedMap("g"); this.big_num = 1n; this.date = new Date(); this.message_without_schema_defined = ""; this.number_without_schema_defined = 0; this.records_without_schema_defined = {}; } @call({}) init_contract({ }) { if (this.is_inited) { near.log(`message inited`); return; } this.is_inited = true; } @view({}) is_contract_inited({}) { near.log(`query is_contract_inited`); return this.is_inited; } @call({}) set_record({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); this.records[account_id] = message; } @view({}) get_record({ account_id }) { near.log(`get_record for account_id ${account_id}`); return this.records[account_id] || null; } @call({}) set_truck_info({ name, speed }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_truck_info name ${name}, speed ${speed}`); let truck = new Truck(); truck.name = name; truck.speed = speed; truck.loads = this.truck.loads; this.truck = truck; let car = new Car(); car.name = name; car.speed = speed; this.user_car_map.set(account_id, car); } @call({}) add_truck_load({ name, load }) { let account_id = near.signerAccountId(); near.log(`${account_id} add_truck_load name ${name}, load ${load}`); this.truck.loads.set(name, load); } @view({}) get_truck_info({ }) { near.log(`get_truck_info`); return this.truck.info(); } @view({}) get_user_car_info({ account_id }) { near.log(`get_user_car_info for account_id ${account_id}`); let car = this.user_car_map.get(account_id); if (car == null) { return null; } return car.info(); } @call({}) push_message({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} push_message message ${message}`); this.messages.push(message); } @view({}) get_messages({ }) { near.log(`get_messages`); return this.messages.join(','); } @call({}) set_nested_efficient_recordes({ message, id }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_nested_efficient_recordes with message ${message},id ${id}`); this.efficient_recordes.set(account_id, message); const nestedMap = this.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }); nestedMap.set(account_id, message); this.nested_efficient_recordes.set(id, nestedMap); const nestedLookup = this.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }); nestedLookup.set(account_id, message); this.nested_lookup_recordes.set(id, nestedLookup); // vector_nested_group: {vector: {value: { lookup_map: {value: 'string'}}}}, const vecNestedLookup = this.vector_nested_group.get(0, { defaultValue: new LookupMap("di_0_"), }); vecNestedLookup.set(account_id, message); if (this.vector_nested_group.isEmpty()) { this.vector_nested_group.push(vecNestedLookup); } else { this.vector_nested_group.replace(0, vecNestedLookup); } const lookupNestVec = this.lookup_nest_vec.get(account_id, { defaultValue: new Vector("ei_" + account_id + "_"), }); lookupNestVec.push(message); this.lookup_nest_vec.set(account_id, lookupNestVec); this.unordered_set.set(account_id); } @call({}) set_big_num_and_date({ bigint_num, new_date }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_bigint_and_date bigint_num ${bigint_num}, new_date: ${new_date}`); this.big_num = bigint_num; this.date = new_date; } @view({}) get_big_num({ }) { near.log(`get_big_num`); return this.big_num; } @view({}) get_date({ }) { near.log(`get_date`); return this.date; } @view({}) get_efficient_recordes({ account_id }) { near.log(`get_efficient_recordes for account_id ${account_id}`); return this.efficient_recordes.get(account_id); } @view({}) get_nested_efficient_recordes({ account_id, id }) { near.log(`get_nested_efficient_recordes for account_id ${account_id}, id ${id}`); return this.nested_efficient_recordes.get(id, { defaultValue: new UnorderedMap("i_" + id + "_"), }).get(account_id); } @view({}) get_nested_lookup_recordes({ account_id, id }) { near.log(`get_nested_lookup_recordes for account_id ${account_id}, id ${id}`); return this.nested_lookup_recordes.get(id, { defaultValue: new LookupMap("li_" + id + "_"), }).get(account_id); } @view({}) get_vector_nested_group({ idx, account_id }) { near.log(`get_vector_nested_group for idx ${idx}, account_id ${account_id}`); return this.vector_nested_group.get(idx).get(account_id); } @view({}) get_lookup_nested_vec({ account_id, idx }) { near.log(`get_looup_nested_vec for account_id ${account_id}, idx ${idx}`); return this.lookup_nest_vec.get(account_id).get(idx); } @view({}) get_is_contains_user({ account_id }) { near.log(`get_is_contains_user for account_id ${account_id}`); return this.unordered_set.contains(account_id); } @call({}) set_extra_data({ message, number }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_extra_data message ${message}, number: ${number}`); this.message_without_schema_defined = message; this.number_without_schema_defined = number; } @view({}) get_extra_msg({ }) { near.log(`get_extra_msg`); return this.message_without_schema_defined; } @view({}) get_extra_number({ }) { near.log(`get_extra_number`); return this.number_without_schema_defined; } @call({}) set_extra_record({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_extra_record with message ${message}`); this.records_without_schema_defined[account_id] = message; } @view({}) get_extra_record({ account_id }) { near.log(`get_extra_record for account_id ${account_id}`); return this.records_without_schema_defined[account_id] || null; } @view({}) get_subtype_of_efficient_recordes({ }) { near.log(`get_subtype_of_efficient_recordes`); return this.efficient_recordes.subtype(); } @view({}) get_subtype_of_nested_efficient_recordes({ }) { near.log(`get_subtype_of_nested_efficient_recordes`); return this.nested_efficient_recordes.subtype(); } @view({}) get_subtype_of_nested_lookup_recordes({ }) { near.log(`get_subtype_of_nested_lookup_recordes`); return this.nested_lookup_recordes.subtype(); } } ================================================ FILE: examples/src/status-message/status-message-borsh.js ================================================ import {NearBindgen, call, view, near} from "near-sdk-js"; import * as borsh from 'borsh'; const schema = { struct: { records: {map: { key: 'string', value: 'string' }} } }; @NearBindgen({ serializer(statusMessage) { return borsh.serialize(schema, statusMessage); }, deserializer(value) { return borsh.deserialize(schema, value); } }) export class StatusMessage { constructor() { this.records = new Map() } @call({}) set_status({ message }) { let account_id = near.signerAccountId() env.log(`${account_id} set_status with message ${message}`) this.records.set(account_id, message) } @view({}) get_status({ account_id }) { env.log(`get_status for account_id ${account_id}`) return this.records.get(account_id) || null } } ================================================ FILE: examples/src/status-message/status-message-collections.js ================================================ import { NearBindgen, call, view, near, LookupSet, UnorderedMap, } from "near-sdk-js"; @NearBindgen({}) export class StatusMessage { constructor() { this.records = new UnorderedMap("a"); this.uniqueValues = new LookupSet("b"); } @call({}) set_status({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); this.records.set(account_id, message); this.uniqueValues.set(message); } @view({}) get_status({ account_id }) { near.log(`get_status for account_id ${account_id}`); return this.records.get(account_id); } @view({}) has_status({ message }) { // used for test LookupMap return this.uniqueValues.contains(message); } @view({}) get_all_statuses() { // used for test UnorderedMap return this.records.toArray(); } } ================================================ FILE: examples/src/status-message/status-message-deserialize-err.js ================================================ import {NearBindgen, call, view, near} from "near-sdk-js"; @NearBindgen({ deserializer(_value) { throw new Error('deserialize err') }, }) export class StatusMessage { constructor() { this.records = new Map() } @call({}) set_status({ message }) { let account_id = near.signerAccountId() env.log(`${account_id} set_status with message ${message}`) this.records.set(account_id, message) } @view({}) get_status({ account_id }) { env.log(`get_status for account_id ${account_id}`) return this.records.get(account_id) || null } } ================================================ FILE: examples/src/status-message/status-message-migrate-add-field.js ================================================ import {NearBindgen, call, view, near, migrate, assert} from "near-sdk-js"; class OldStatusMessage { constructor() { this.records = {}; } } @NearBindgen({}) export class StatusMessage { constructor() { this.records = {}; this.new_fields = {}; } @call({}) set_status({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); this.records[account_id] = message; } @call({}) set_new_status({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_new_status with message ${message}`); this.new_fields[account_id] = message; } @view({}) get_status({ account_id }) { near.log(`get_status for account_id ${account_id}`); return this.records[account_id] || null; } @view({}) get_new_status({ account_id }) { near.log(`get_new_status for account_id ${account_id}`); return this.records[account_id] || null; } // Migrate from OldStatusMessage to StatusMessage @migrate({}) migrateState() { assert(this.records !== undefined, "Contract state should not be deserialized in @migrate"); // retrieve the current state from the contract let records = JSON.parse(near.storageRead("STATE")).records; this.records = records; this.new_fields = {}; } } ================================================ FILE: examples/src/status-message/status-message-serialize-err.js ================================================ import {NearBindgen, call, view, near} from "near-sdk-js"; @NearBindgen({ serializer(_value) { throw new Error('serialize err') }, }) export class StatusMessage { constructor() { this.records = new Map() } @call({}) set_status({ message }) { let account_id = near.signerAccountId() env.log(`${account_id} set_status with message ${message}`) this.records.set(account_id, message) } @view({}) get_status({ account_id }) { env.log(`get_status for account_id ${account_id}`) return this.records.get(account_id) || null } } ================================================ FILE: examples/src/status-message/status-message.js ================================================ import { NearBindgen, call, view, near } from "near-sdk-js"; @NearBindgen({}) export class StatusMessage { constructor() { this.records = {}; } @call({}) set_status({ message }) { let account_id = near.signerAccountId(); near.log(`${account_id} set_status with message ${message}`); this.records[account_id] = message; } @view({}) get_status({ account_id }) { near.log(`get_status for account_id ${account_id}`); return this.records[account_id] || null; } } ================================================ FILE: examples/tsconfig.json ================================================ { "compilerOptions": { "experimentalDecorators": true, "target": "es2020", "moduleResolution": "node", "noEmit": true }, "exclude": ["node_modules"] } ================================================ FILE: generate-docs-markdown.js ================================================ const { readFileSync, writeFileSync, unlinkSync } = require("fs"); const { execSync } = require("child_process"); // Load the base configuration from typedoc.json const baseConfig = JSON.parse(readFileSync("./typedoc.json", "utf8")); // Extend the base configuration for Markdown documentation const markdownConfig = { ...baseConfig, githubPages: false, out: "markdown-docs", plugin: ["typedoc-plugin-markdown"], }; // Write the extended configuration to a temporary file const configFilePath = "./typedoc-markdown.json"; writeFileSync(configFilePath, JSON.stringify(markdownConfig, null, 2)); // Run TypeDoc with the extended configuration try { execSync(`npx typedoc --options ${configFilePath}`, { stdio: "inherit" }); } finally { // Clean up the temporary file unlinkSync(configFilePath); } ================================================ FILE: near-sdk-js@2.0.0-diff-1.0.0.md ================================================ # Breaking features diff from SDK 2.0.0 to 1.0.0 ## borsh data de/serializer for contract state * using for: new contracts or migrate from a borsh serialized contract [example](https://github.com/near/near-sdk-js/blob/develop/examples/src/status-message/status-message-borsh.js) ```js @NearBindgen({ serializer(statusMessage) { return borsh.serialize(schema, statusMessage); }, deserializer(value) { return borsh.deserialize(schema, value); } }) export class StatusMessage { constructor() { this.records = new Map() } @call({}) set_status({ message }) { let account_id = near.signerAccountId() env.log(`${account_id} set_status with message ${message}`) this.records.set(account_id, message) } @view({}) get_status({ account_id }) { env.log(`get_status for account_id ${account_id}`) return this.records.get(account_id) || null } } ``` ## js contract migration with data fields ### example1 * using for: contract state migrations * [example](https://github.com/near/near-sdk-js/blob/develop/examples/src/status-message/status-message-migrate-add-field.js) ```js import {NearBindgen, call, view, near, migrate, Vector, assert} from "near-sdk-js"; class OldStatusMessage { constructor() { this.records = {}; } } @NearBindgen({}) export class StatusMessage { constructor() { this.records = {}; this.new_fields = {}; // new added filed } ... // Migrate from OldStatusMessage to StatusMessage @migrate({}) migrateState() { assert(this.records !== undefined, "Contract state should not be deserialized in @migrate"); // retrieve the current state from the contract let records = JSON.parse(near.storageRead("STATE")).records; this.records = records; this.new_fields = {}; } } ``` ### example2 * another migration example can be found in [test-basic-updates.ava.js](./examples/__tests__/test-basic-updates.ava.js) with [old state contract](./examples/src/basic-updates/basic-updates-base.js) and new [new state contract](./examples/src/basic-updates/basic-updates-update.js). with the test command in [examples directory](./examples): ```shell pnpm run test:basic-updates ``` ## auto reconstruct class from object by static json schema * use for contract serialzed by default method(json), the detail document is here: https://github.com/near/near-sdk-js/blob/develop/AUTO_RECONSCTRUCT_BY_JSON_SCHEME.md#the-schema-format * [example](https://github.com/near/near-sdk-js/blob/develop/examples/src/status-deserialize-class.js#L49) ```js class Car { static schema = { name: "string", speed: "number", }; constructor() { this.name = ""; this.speed = 0; } info() { return this.name + " run with speed " + this.speed.toString() } } class Truck { static schema = { name: "string", speed: "number", loads: UnorderedMap }; constructor() { this.name = ""; this.speed = 0; this.loads = new UnorderedMap("tra"); } info() { return this.name + " run with speed " + this.speed.toString() + " with loads length: " + this.loads.toArray().length; } } // sdk should first try if UnorderedMap has a static schema and use it to recursively decode. @NearBindgen({}) export class StatusDeserializeClass { static schema = { truck: Truck, efficient_recordes: UnorderedMap, nested_efficient_recordes: {class: UnorderedMap, value: UnorderedMap}, nested_lookup_recordes: {class: UnorderedMap, value: LookupMap}, vector_nested_group: {class: Vector, value: LookupMap}, lookup_nest_vec: { class: LookupMap, value: Vector }, unordered_set: UnorderedSet, user_car_map: {class: UnorderedMap, value: Car }, big_num: 'bigint', date: 'date' }; constructor() { this.is_inited = false; this.records = {}; this.truck = new Truck(); this.messages = []; this.efficient_recordes = new UnorderedMap("a"); this.nested_efficient_recordes = new UnorderedMap("b"); this.nested_lookup_recordes = new UnorderedMap("c"); this.vector_nested_group = new Vector("d"); this.lookup_nest_vec = new LookupMap("e"); this.unordered_set = new UnorderedSet("f"); this.user_car_map = new UnorderedMap("g"); this.big_num = 1n; this.date = new Date(); this.message_without_schema_defined = ""; this.number_without_schema_defined = 0; this.records_without_schema_defined = {}; } ... } ``` ## query data Nesting of objects in SDK Collections without Nested Collection's reconstructor * using for SDK Collections which has description in static json schema In [near-sdk-js 1.0.0](https://www.npmjs.com/package/near-sdk-js/v/1.0.0), users needed to call a `reconstructor` method in order for **Nested Collections** to be properly decoded: ```typescript @NearBindgen({}) export class Contract { outerMap: UnorderedMap>; constructor() { this.outerMap = new UnorderedMap("o"); } @view({}) get({id, accountId}: { id: string; accountId: string }) { const innerMap = this.outerMap.get(id, { reconstructor: UnorderedMap.reconstruct, // we need to announce reconstructor explicit }); if (innerMap === null) { return null; } return innerMap.get(accountId); } } ``` In [near-sdk-js 2.0.0](https://www.npmjs.com/package/near-sdk-js/v/2.0.0) With schemas, this is no longer needed, as the SDK can correctly infer how to decode the Nested Collections: ```typescript @NearBindgen({}) export class Contract { static schema = { outerMap: {class: UnorderedMap, value: UnorderedMap} }; outerMap: UnorderedMap>; constructor() { this.outerMap = new UnorderedMap("o"); } @view({}) get({id, accountId}: { id: string; accountId: string }) { const innerMap = this.outerMap.get(id); // reconstructor can be infered from static schema if (innerMap === null) { return null; } return innerMap.get(accountId); } } ``` ================================================ FILE: package.json ================================================ { "private": true, "engines": { "node": ">=14 <16.6.0 || >16.6.0", "pnpm": ">=7" }, "scripts": { "preinstall": "npx only-allow pnpm", "build": "turbo run build", "test": "turbo run test", "lint": "turbo run lint", "format": "turbo run format", "docs:generate": "typedoc", "docs:generate-markdown": "node generate-docs-markdown.js" }, "devDependencies": { "turbo": "1.10.16", "typedoc": "0.26.6", "typedoc-plugin-markdown": "4.2.6", "typescript": "4.7.4" } } ================================================ FILE: packages/near-contract-standards/.gitignore ================================================ build node_modules ================================================ FILE: packages/near-contract-standards/README.md ================================================ # Package for NEAR JS contract standards This package provides a set of interfaces and implementations for NEAR's contract standards: - NFT - FT (NEP-141) ================================================ FILE: packages/near-contract-standards/ava.config.cjs ================================================ require("util").inspect.defaultOptions.depth = 5; // Increase AVA's printing depth module.exports = { timeout: "300000", files: ["**/*.ava.js"], failWithoutAssertions: false, extensions: ["js"], }; ================================================ FILE: packages/near-contract-standards/lib/event.d.ts ================================================ export declare abstract class NearEvent { private internal_to_json_string; private internal_to_json_event_string; /** * Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; } ================================================ FILE: packages/near-contract-standards/lib/event.js ================================================ import { near } from "near-sdk-js"; export class NearEvent { internal_to_json_string() { return JSON.stringify(this); } internal_to_json_event_string() { return `EVENT_JSON: ${this.internal_to_json_string()}`; } /** * Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { near.log(this.internal_to_json_event_string()); } } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/core.d.ts ================================================ import { AccountId, PromiseOrValue, Balance } from "near-sdk-js"; import { Option } from "../non_fungible_token/utils"; /** * The core methods for a basic fungible token. Extension standards may be * added in addition to this interface. * * # Examples * * ```typescript * import { near, call, view, AccountId, PromiseOrValue, Balance } from "near-sdk-js"; * import { Option } from "../non_fungible_token/utils"; * import { FungibleTokenCore, FungibleToken } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({}) * ft_transfer({ receiver_id, amount, memo }: { * receiver_id: AccountId, * amount: Balance, * memo?: String * }): void { * this.token.ft_transfer({ receiver_id, amount, memo }); * } * * @call({}) * ft_transfer_call({ receiver_id, amount, memo, msg }: { * receiver_id: AccountId, * amount: Balance, * memo?: Option, * msg: String * }): PromiseOrValue { * return this.token.ft_transfer_call({ receiver_id, amount, memo, msg }); * } * * @view({}) * ft_total_supply(): Balance { * return this.token.ft_total_supply(); * } * * @view({}) * ft_balance_of({ account_id }: { account_id: AccountId }): Balance { * return this.token.ft_balance_of({ account_id }); * } * } * ``` */ export interface FungibleTokenCore { /** * Transfers positive `amount` of tokens from the `near.predecessorAccountId()` to `receiver_id`. * Both accounts must be registered with the contract for transfer to succeed. (See [NEP-145](https://github.com/near/NEPs/discussions/145)) * This method must to be able to accept attached deposits, and must not panic on attached deposit. * Exactly 1 yoctoNEAR must be attached. * See [the Security section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Arguments: * @param receiver_id - the account ID of the receiver. * @param amount - the amount of tokens to transfer. Must be a positive number in decimal string representation. * @param memo - an optional string field in a free form to associate a memo with this transfer. */ ft_transfer({ receiver_id, amount, memo }: { receiver_id: AccountId; amount: Balance; memo?: String; }): any; /** * Transfers positive `amount` of tokens from the `near.predecessorAccountId()` to `receiver_id` account. Then * calls `ft_on_transfer` method on `receiver_id` contract and attaches a callback to resolve this transfer. * `ft_on_transfer` method must return the amount of tokens unused by the receiver contract, the remaining tokens * must be refunded to the `predecessor_account_id` at the resolve transfer callback. * * Token contract must pass all the remaining unused gas to the `ft_on_transfer` call. * * Malicious or invalid behavior by the receiver's contract: * - If the receiver contract promise fails or returns invalid value, the full transfer amount must be refunded. * - If the receiver contract overspent the tokens, and the `receiver_id` balance is lower than the required refund * amount, the remaining balance must be refunded. See [the Security section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Both accounts must be registered with the contract for transfer to succeed. (See #145) * This method must to be able to accept attached deposits, and must not panic on attached deposit. Exactly 1 yoctoNEAR must be attached. See [the Security * section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Arguments: * @param receiver_id - the account ID of the receiver contract. This contract will be called. * @param amount - the amount of tokens to transfer. Must be a positive number in a decimal string representation. * @param memo - an optional string field in a free form to associate a memo with this transfer. * @param msg - a string message that will be passed to `ft_on_transfer` contract call. * * @returns a promise which will result in the amount of tokens withdrawn from sender's account. */ ft_transfer_call({ receiver_id, amount, memo, msg }: { receiver_id: AccountId; amount: Balance; memo: Option; msg: String; }): PromiseOrValue; /** Returns the total supply of the token in a decimal string representation. */ ft_total_supply(): Balance; /** Returns the balance of the account. If the account doesn't exist must returns `"0"`. */ ft_balance_of({ account_id }: { account_id: AccountId; }): Balance; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/core.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/fungible_token/core_impl.d.ts ================================================ import { StorageBalance, StorageBalanceBounds, StorageManagement } from "../storage_management"; import { FungibleTokenCore } from "./core"; import { FungibleTokenResolver } from "./resolver"; import { AccountId, LookupMap, Balance, PromiseOrValue, StorageUsage, IntoStorageKey } from "near-sdk-js"; import { Option } from '../non_fungible_token/utils'; /** Implementation of a FungibleToken standard * Allows to include NEP-141 compatible token to any contract. * There are next interfaces that any contract may implement: * - FungibleTokenCore -- interface with ft_transfer methods. FungibleToken provides methods for it. * - FungibleTokenMetaData -- return metadata for the token in NEP-148, up to contract to implement. * - StorageManager -- interface for NEP-145 for allocating storage per account. FungibleToken provides methods for it. * - AccountRegistrar -- interface for an account to register and unregister * * For example usage, see examples/src/fungible-token/my-ft.ts */ export declare class FungibleToken implements FungibleTokenCore, StorageManagement, FungibleTokenResolver { accounts: LookupMap; total_supply: Balance; account_storage_usage: StorageUsage; constructor(); init(prefix: IntoStorageKey): this; measure_account_storage_usage(): void; internal_unwrap_balance_of(account_id: AccountId): Balance; internal_deposit(account_id: AccountId, amount: Balance): void; internal_withdraw(account_id: AccountId, amount: Balance): void; internal_transfer(sender_id: AccountId, receiver_id: AccountId, amount: Balance, memo?: String): void; internal_register_account(account_id: AccountId): void; /** Internal method that returns the amount of burned tokens in a corner case when the sender * has deleted (unregistered) their account while the `ft_transfer_call` was still in flight. * Returns (Used token amount, Burned token amount) */ internal_ft_resolve_transfer(sender_id: AccountId, receiver_id: AccountId, amount: Balance): [bigint, bigint]; /** Implementation of FungibleTokenCore */ ft_transfer({ receiver_id, amount, memo }: { receiver_id: AccountId; amount: Balance; memo?: String; }): void; ft_transfer_call({ receiver_id, amount, memo, msg }: { receiver_id: AccountId; amount: Balance; memo: Option; msg: string; }): PromiseOrValue; ft_total_supply(): Balance; ft_balance_of({ account_id }: { account_id: AccountId; }): Balance; /** Implementation of storage * Internal method that returns the Account ID and the balance in case the account was * unregistered. */ internal_storage_unregister(force?: boolean): Option<[AccountId, Balance]>; internal_storage_balance_of(account_id: AccountId): Option; /** Implementation of StorageManagement * @param registration_only doesn't affect the implementation for vanilla fungible token. */ storage_deposit({ account_id, registration_only, }: { account_id?: AccountId; registration_only?: boolean; }): StorageBalance; /** * While storage_withdraw normally allows the caller to retrieve `available` balance, the basic * Fungible Token implementation sets storage_balance_bounds.min == storage_balance_bounds.max, * which means available balance will always be 0. So this implementation: * - panics if `amount > 0` * - never transfers Ⓝ to caller * - returns a `storage_balance` struct if `amount` is 0 */ storage_withdraw({ amount }: { amount?: bigint; }): StorageBalance; storage_unregister({ force }: { force?: boolean; }): boolean; storage_balance_bounds(): StorageBalanceBounds; storage_balance_of({ account_id }: { account_id: AccountId; }): Option; /** Implementation of FungibleTokenResolver */ ft_resolve_transfer({ sender_id, receiver_id, amount }: { sender_id: AccountId; receiver_id: AccountId; amount: Balance; }): Balance; bigIntMax: (...args: bigint[]) => bigint; bigIntMin: (...args: bigint[]) => bigint; static reconstruct(data: FungibleToken): FungibleToken; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/core_impl.js ================================================ import { StorageBalance, StorageBalanceBounds } from "../storage_management"; import { FtBurn, FtTransfer } from "./events"; import { near, LookupMap, NearPromise, assert, } from "near-sdk-js"; // TODO: move to the main SDK package import { assert_one_yocto } from "../non_fungible_token/utils"; const GAS_FOR_RESOLVE_TRANSFER = 15000000000000n; const GAS_FOR_FT_TRANSFER_CALL = 25000000000000n + GAS_FOR_RESOLVE_TRANSFER; const ERR_TOTAL_SUPPLY_OVERFLOW = "Total supply overflow"; /** Implementation of a FungibleToken standard * Allows to include NEP-141 compatible token to any contract. * There are next interfaces that any contract may implement: * - FungibleTokenCore -- interface with ft_transfer methods. FungibleToken provides methods for it. * - FungibleTokenMetaData -- return metadata for the token in NEP-148, up to contract to implement. * - StorageManager -- interface for NEP-145 for allocating storage per account. FungibleToken provides methods for it. * - AccountRegistrar -- interface for an account to register and unregister * * For example usage, see examples/src/fungible-token/my-ft.ts */ export class FungibleToken { constructor() { this.bigIntMax = (...args) => args.reduce((m, e) => e > m ? e : m); this.bigIntMin = (...args) => args.reduce((m, e) => e < m ? e : m); this.accounts = new LookupMap(""); this.total_supply = 0n; this.account_storage_usage = 0n; } init(prefix) { const storage_prefix = prefix.into_storage_key(); this.accounts = new LookupMap(storage_prefix); this.total_supply = 0n; this.account_storage_usage = 0n; this.measure_account_storage_usage(); return this; } measure_account_storage_usage() { let initial_storage_usage = near.storageUsage(); let tmp_account_id = "a".repeat(64); this.accounts.set(tmp_account_id, 0n); this.account_storage_usage = near.storageUsage() - initial_storage_usage; this.accounts.remove(tmp_account_id); } internal_unwrap_balance_of(account_id) { let balance = this.accounts.get(account_id); if (balance === null) { throw Error(`The account ${account_id} is not registered`); } return BigInt(balance); } internal_deposit(account_id, amount) { let balance = BigInt(this.internal_unwrap_balance_of(account_id)); let new_balance = balance + amount; this.accounts.set(account_id, new_balance); let new_total_supply = this.total_supply + amount; this.total_supply = new_total_supply; } internal_withdraw(account_id, amount) { let balance = BigInt(this.internal_unwrap_balance_of(account_id)); let new_balance = balance - amount; if (new_balance < 0) { throw Error("The account doesn't have enough balance"); } this.accounts.set(account_id, new_balance); let new_total_supply = this.total_supply - amount; this.total_supply = new_total_supply; } internal_transfer(sender_id, receiver_id, amount, memo) { assert(sender_id != receiver_id, "Sender and receiver should be different"); assert(amount > 0, "The amount should be a positive number"); this.internal_withdraw(sender_id, amount); this.internal_deposit(receiver_id, amount); new FtTransfer(sender_id, receiver_id, amount, memo).emit(); } internal_register_account(account_id) { if (this.accounts.containsKey(account_id)) { throw Error("The account is already registered"); } this.accounts.set(account_id, 0n); } /** Internal method that returns the amount of burned tokens in a corner case when the sender * has deleted (unregistered) their account while the `ft_transfer_call` was still in flight. * Returns (Used token amount, Burned token amount) */ internal_ft_resolve_transfer(sender_id, receiver_id, amount) { // Get the unused amount from the `ft_on_transfer` call result. let unused_amount; try { const promise_result = near.promiseResult(0).replace(/"*/g, ''); //TODO: why promiseResult returns result with brackets? unused_amount = this.bigIntMin(amount, BigInt(promise_result)); } catch (e) { if (e.message.includes('Failed')) { unused_amount = amount; } else { throw e; } } if (unused_amount > 0) { let receiver_balance = BigInt(this.accounts.get(receiver_id) ?? 0); if (receiver_balance > 0n) { let refund_amount = this.bigIntMin(receiver_balance, unused_amount); let new_receiver_balance = receiver_balance - refund_amount; if (new_receiver_balance < 0n) { throw Error("The receiver account doesn't have enough balance"); } this.accounts.set(receiver_id, new_receiver_balance); let sender_balance = this.accounts.get(sender_id); if (sender_balance) { sender_balance = BigInt(sender_balance); let new_sender_balance = sender_balance + refund_amount; this.accounts.set(sender_id, new_sender_balance); new FtTransfer(receiver_id, sender_id, refund_amount, "refund").emit(); let used_amount = amount - refund_amount; if (used_amount < 0n) { throw Error(ERR_TOTAL_SUPPLY_OVERFLOW); } return [used_amount.valueOf(), 0n]; } else { const new_total_supply = this.total_supply - refund_amount; if (new_total_supply < 0) { throw Error(ERR_TOTAL_SUPPLY_OVERFLOW); } this.total_supply = new_total_supply; near.log("The account of the sender was deleted"); new FtBurn(receiver_id, refund_amount, "refund").emit(); return [amount, refund_amount]; } } } return [amount, 0n]; } /** Implementation of FungibleTokenCore */ ft_transfer({ receiver_id, amount, memo }) { amount = BigInt(amount); assert_one_yocto(); let sender_id = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, amount, memo); } ft_transfer_call({ receiver_id, amount, memo, msg }) { amount = BigInt(amount); assert_one_yocto(); assert(near.prepaidGas() > GAS_FOR_FT_TRANSFER_CALL, "More gas is required"); let sender_id = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, amount, memo); let receiver_gas = near.prepaidGas() - GAS_FOR_FT_TRANSFER_CALL; if (receiver_gas < 0) { throw new Error("Prepaid gas overflow"); } return NearPromise.new(receiver_id) .functionCall("ft_on_transfer", JSON.stringify({ sender_id, amount: String(amount), msg }), BigInt(0), receiver_gas) .then(NearPromise.new(near.currentAccountId()) .functionCall("ft_resolve_transfer", JSON.stringify({ sender_id, receiver_id, amount: String(amount) }), BigInt(0), GAS_FOR_RESOLVE_TRANSFER)); } ft_total_supply() { return this.total_supply; } ft_balance_of({ account_id }) { return this.accounts.get(account_id) ?? 0n; } /** Implementation of storage * Internal method that returns the Account ID and the balance in case the account was * unregistered. */ internal_storage_unregister(force) { assert_one_yocto(); let account_id = near.predecessorAccountId(); let balance = BigInt(this.accounts.get(account_id)); if (balance || balance == 0n) { if (balance == 0n || force) { this.accounts.remove(account_id); this.total_supply = this.total_supply - balance; NearPromise.new(account_id).transfer(this.storage_balance_bounds().min + 1n); return [account_id, balance]; } else { throw Error("Can't unregister the account with the positive balance without force"); } } else { near.log(`The account ${account_id} is not registered`); return null; } } internal_storage_balance_of(account_id) { if (this.accounts.containsKey(account_id)) { return new StorageBalance(this.storage_balance_bounds().min, 0n); } else { return null; } } /** Implementation of StorageManagement * @param registration_only doesn't affect the implementation for vanilla fungible token. */ storage_deposit({ account_id, registration_only, }) { let amount = near.attachedDeposit(); account_id = account_id ?? near.predecessorAccountId(); if (this.accounts.containsKey(account_id)) { near.log("The account is already registered, refunding the deposit"); if (amount > 0) { NearPromise.new(near.predecessorAccountId()).transfer(amount); } } else { let min_balance = this.storage_balance_bounds().min; if (amount < min_balance) { throw Error("The attached deposit is less than the minimum storage balance"); } this.internal_register_account(account_id); let refund = amount - min_balance; if (refund > 0) { NearPromise.new(near.predecessorAccountId()).transfer(refund); } } return this.internal_storage_balance_of(account_id); } /** * While storage_withdraw normally allows the caller to retrieve `available` balance, the basic * Fungible Token implementation sets storage_balance_bounds.min == storage_balance_bounds.max, * which means available balance will always be 0. So this implementation: * - panics if `amount > 0` * - never transfers Ⓝ to caller * - returns a `storage_balance` struct if `amount` is 0 */ storage_withdraw({ amount }) { amount = BigInt(amount); assert_one_yocto(); let predecessor_account_id = near.predecessorAccountId(); const storage_balance = this.internal_storage_balance_of(predecessor_account_id); if (storage_balance) { if (amount && amount > 0) { throw Error("The amount is greater than the available storage balance"); } return storage_balance; } else { throw Error(`The account ${predecessor_account_id} is not registered`); } } storage_unregister({ force }) { return this.internal_storage_unregister(force) ? true : false; } storage_balance_bounds() { let required_storage_balance = this.account_storage_usage * near.storageByteCost(); return new StorageBalanceBounds(required_storage_balance, required_storage_balance); } storage_balance_of({ account_id }) { return this.internal_storage_balance_of(account_id); } /** Implementation of FungibleTokenResolver */ ft_resolve_transfer({ sender_id, receiver_id, amount }) { amount = BigInt(amount); const res = this.internal_ft_resolve_transfer(sender_id, receiver_id, amount); const used_amount = res[0]; const burned_amount = res[1]; if (burned_amount > 0) { near.log(`Account @${sender_id} burned ${burned_amount}`); } return used_amount; } static reconstruct(data) { const ret = new FungibleToken(); Object.assign(ret, data); if (ret.accounts) { ret.accounts = LookupMap.reconstruct(ret.accounts); } if (ret.total_supply) { ret.total_supply = BigInt(ret.total_supply); } if (ret.account_storage_usage) { ret.account_storage_usage = BigInt(ret.account_storage_usage); } return ret; } } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/events.d.ts ================================================ /** * Standard for nep141 (Fungible Token) events. * * These events will be picked up by the NEAR indexer. * * * * This is an extension of the events format (nep-297): * * * The three events in this standard are [`FtMint`], [`FtTransfer`], and [`FtBurn`]. * * These events can be logged by calling `.emit()` on them if a single event, or calling * [`FtMint::emit_many`], [`FtTransfer::emit_many`], * or [`FtBurn::emit_many`] respectively. */ import { NearEvent } from "../event"; import { Option } from "../non_fungible_token/utils"; import { AccountId, Balance } from "near-sdk-js"; export declare type Nep141EventKind = FtMint[] | FtTransfer[] | FtBurn[]; export declare class Nep141Event extends NearEvent { standard: string; version: string; event: string; data: Nep141EventKind; constructor(version: string, event_kind: Nep141EventKind); } /** Data to log for an FT mint event. To log this event, call [`.emit()`](FtMint::emit). */ export declare class FtMint { owner_id: AccountId; amount: number; memo: Option; constructor(owner_id: AccountId, amount: number, memo: Option); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an FT mint event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtMint`] represents the data of each mint. */ emit_many(data: FtMint[]): void; } /** Data to log for an FT transfer event. To log this event, * call [`.emit()`](FtTransfer::emit). */ export declare class FtTransfer { old_owner_id: AccountId; new_owner_id: AccountId; amount: string; memo: Option; constructor(old_owner_id: AccountId, new_owner_id: AccountId, amount: bigint, memo: Option); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an FT transfer event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtTransfer`] represents the data of each transfer. */ emit_many(data: FtTransfer[]): void; } /** Data to log for an FT burn event. To log this event, call [`.emit()`](FtBurn::emit). */ export declare class FtBurn { owner_id: AccountId; amount: string; memo: Option; constructor(owner_id: AccountId, amount: Balance, memo: Option); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an FT burn event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtBurn`] represents the data of each burn. */ emit_many(data: FtBurn[]): void; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/events.js ================================================ /** * Standard for nep141 (Fungible Token) events. * * These events will be picked up by the NEAR indexer. * * * * This is an extension of the events format (nep-297): * * * The three events in this standard are [`FtMint`], [`FtTransfer`], and [`FtBurn`]. * * These events can be logged by calling `.emit()` on them if a single event, or calling * [`FtMint::emit_many`], [`FtTransfer::emit_many`], * or [`FtBurn::emit_many`] respectively. */ import { NearEvent } from "../event"; import { toSnakeCase } from "../util"; export class Nep141Event extends NearEvent { constructor(version, event_kind) { super(); this.standard = "nep141"; this.version = version; this.event = toSnakeCase(event_kind[0].constructor.name); this.data = event_kind; } } /** Data to log for an FT mint event. To log this event, call [`.emit()`](FtMint::emit). */ export class FtMint { constructor(owner_id, amount, memo) { this.owner_id = owner_id; this.amount = amount; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]); } /** Emits an FT mint event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtMint`] represents the data of each mint. */ emit_many(data) { new_141_v1(data).emit(); } } /** Data to log for an FT transfer event. To log this event, * call [`.emit()`](FtTransfer::emit). */ export class FtTransfer { constructor(old_owner_id, new_owner_id, amount, memo) { this.old_owner_id = old_owner_id; this.new_owner_id = new_owner_id; this.amount = amount.toString(); this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]); } /** Emits an FT transfer event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtTransfer`] represents the data of each transfer. */ emit_many(data) { new_141_v1(data).emit(); } } /** Data to log for an FT burn event. To log this event, call [`.emit()`](FtBurn::emit). */ export class FtBurn { constructor(owner_id, amount, memo) { this.owner_id = owner_id; this.amount = amount.toString(); this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]); } /** Emits an FT burn event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtBurn`] represents the data of each burn. */ emit_many(data) { new_141_v1(data).emit(); } } function new_141(version, event_kind) { return new Nep141Event(version, event_kind); } function new_141_v1(event_kind) { return new_141("1.0.0", event_kind); } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/index.d.ts ================================================ export * from './core_impl'; export * from './core'; export * from './events'; export * from './metadata'; export * from './receiver'; export * from './resolver'; ================================================ FILE: packages/near-contract-standards/lib/fungible_token/index.js ================================================ export * from './core_impl'; export * from './core'; export * from './events'; export * from './metadata'; export * from './receiver'; export * from './resolver'; ================================================ FILE: packages/near-contract-standards/lib/fungible_token/metadata.d.ts ================================================ import { Option } from "../non_fungible_token/utils"; /** * Return metadata for the token, up to contract to implement. */ export declare class FungibleTokenMetadata { spec: string; name: string; symbol: string; icon: Option; reference: Option; reference_hash: Option; decimals: number; constructor(spec: string, name: string, symbol: string, icon: Option, referance: Option, referance_hash: Option, decimals: number); assert_valid(): void; } export interface FungibleTokenMetadataProvider { ft_metadata(): FungibleTokenMetadata; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/metadata.js ================================================ import { assert, } from "near-sdk-js"; const FT_METADATA_SPEC = "ft-1.0.0"; /** * Return metadata for the token, up to contract to implement. */ export class FungibleTokenMetadata { constructor(spec, name, symbol, icon, referance, referance_hash, decimals) { this.spec = spec; this.name = name; this.symbol = symbol; this.icon = icon; this.reference = referance; this.reference_hash = referance_hash; this.decimals = decimals; } assert_valid() { assert(this.spec == FT_METADATA_SPEC, "Invalid FT_METADATA_SPEC"); const isReferenceProvided = this.reference ? true : false; const isReferenceHashProvided = this.reference_hash ? true : false; assert(isReferenceHashProvided === isReferenceProvided, "reference and reference_hash must be either both provided or not"); if (this.reference_hash) { assert(this.reference_hash.length === 32, "reference_hash must be 32 bytes"); } } } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/receiver.d.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; /** * Provides token transfer resolve functionality. * * # Examples * * ```typescript * import { AccountId, PromiseOrValue } from "near-sdk-js"; * import { FungibleTokenCore, FungibleToken, FungibleTokenReceiver } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore, FungibleTokenReceiver { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({}) * ft_on_transfer({ sender_id, amount, msg }: { * sender_id: AccountId; * amount: number; * msg: String; * }): PromiseOrValue { * return this.token.ft_on_transfer({ sender_id, amount, msg }); * }; * } * ``` */ export interface FungibleTokenReceiver { /** * Called by fungible token contract after `ft_transfer_call` was initiated by * `sender_id` of the given `amount` with the transfer message given in `msg` field. * The `amount` of tokens were already transferred to this contract account and ready to be used. * * The method must return the amount of tokens that are *not* used/accepted by this contract from the transferred * amount. Examples: * - The transferred amount was `500`, the contract completely takes it and must return `0`. * - The transferred amount was `500`, but this transfer call only needs `450` for the action passed in the `msg` * field, then the method must return `50`. * - The transferred amount was `500`, but the action in `msg` field has expired and the transfer must be * cancelled. The method must return `500` or panic. * * Arguments: * @param sender_id - the account ID that initiated the transfer. * @param amount - the amount of tokens that were transferred to this account in a decimal string representation. * @param msg - a string message that was passed with this transfer call. * * @returns the amount of unused tokens that should be returned to sender, in a decimal string representation. */ ft_on_transfer({ sender_id, amount, msg }: { sender_id: AccountId; amount: number; msg: String; }): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/receiver.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/fungible_token/resolver.d.ts ================================================ import { AccountId, Balance } from "near-sdk-js"; /** * Provides token transfer resolve functionality. * * # Examples * * ```typescript * import { AccountId, Balance, call } from "near-sdk-js"; * import { * FungibleTokenCore, * FungibleTokenResolver, * FungibleToken, * } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore, FungibleTokenResolver { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({ privateFunction: true }) * ft_resolve_transfer({ * sender_id, * receiver_id, * amount, * }: { * sender_id: AccountId, * receiver_id: AccountId, * amount: Balance * }): Balance { * const { used_amount, burned_amount } = this.token.internal_ft_resolve_transfer(sender_id, receiver_id, amount); * if (burned_amount > 0) { * console.log(`Account @${sender_id} burned ${burned_amount}`); * } * return used_amount; * } * } * ``` */ export interface FungibleTokenResolver { /** * Resolves the transfer of tokens between `sender_id` and `receiver_id`. * * @param sender_id - The account ID of the sender. * @param receiver_id - The account ID of the receiver. * @param amount - The amount of tokens to resolve in a decimal string representation. * * @returns The amount of tokens used during the transfer, returning the balance as a `Balance`. */ ft_resolve_transfer({ sender_id, receiver_id, amount, }: { sender_id: AccountId; receiver_id: AccountId; amount: Balance; }): Balance; } ================================================ FILE: packages/near-contract-standards/lib/fungible_token/resolver.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/index.d.ts ================================================ /** Non-fungible tokens as described in [by the spec](https://nomicon.io/Standards/NonFungibleToken). */ export * from "./non_fungible_token"; /** Fungible tokens as described in [by the spec](https://nomicon.io/Standards/FungibleToken). */ export * from "./fungible_token"; export * from "./storage_management"; ================================================ FILE: packages/near-contract-standards/lib/index.js ================================================ /** Non-fungible tokens as described in [by the spec](https://nomicon.io/Standards/NonFungibleToken). */ export * from "./non_fungible_token"; /** Fungible tokens as described in [by the spec](https://nomicon.io/Standards/FungibleToken). */ export * from "./fungible_token"; export * from "./storage_management"; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/approval/approval_receiver.d.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; import { TokenId } from "../token"; /** Approval receiver is the interface for the method called (or attempted to be called) when an NFT contract adds an approval for an account. */ export interface NonFungibleTokenApprovalReceiver { /** Respond to notification that contract has been granted approval for a token. * * Notes * - Contract knows the token contract ID from `predecessor_account_id` * * @param token_id - The token to which this contract has been granted approval * @param owner_id - The owner of the token * @param approval_id - The approval ID stored by NFT contract for this approval. * Expected to be a number within the 2^53 limit representable by JSON. * @param msg: - specifies information needed by the approved contract in order to handle the approval. Can indicate both a function to call and the parameters to pass to that function. */ nft_on_approve({ token_id, owner_id, approval_id, msg, }: { token_id: TokenId; owner_id: AccountId; approval_id: bigint; msg: string; }): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/approval/approval_receiver.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/approval/index.d.ts ================================================ import { AccountId, NearPromise } from "near-sdk-js"; import { TokenId } from "../token"; import { Option } from "../utils"; /** Interface used when it's desired to have a non-fungible token that has a * traditional escrow or approval system. This allows Alice to allow Bob * to take only the token with the unique identifier "19" but not others. * It should be noted that in the [core non-fungible token standard] there * is a method to do "transfer and call" which may be preferred over using * an approval management standard in certain use cases. * * [approval management standard]: https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html * [core non-fungible token standard]: https://nomicon.io/Standards/NonFungibleToken/Core.html */ export interface NonFungibleTokenApproval { /** Add an approved account for a specific token. * * Requirements * - Caller of the method must attach a deposit of at least 1 yoctoⓃ for * security purposes * - Contract MAY require caller to attach larger deposit, to cover cost of * storing approver data * - Contract MUST panic if called by someone other than token owner * - Contract MUST panic if addition would cause `nft_revoke_all` to exceed * single-block gas limit * - Contract MUST increment approval ID even if re-approving an account * - If successfully approved or if had already been approved, and if `msg` is * present, contract MUST call `nft_on_approve` on `account_id`. See * `nft_on_approve` description below for details. * * @param token_id - The token for which to add an approval * @param account_id - The account to add to `approvals` * @param msg - Optional string to be passed to `nft_on_approve` * @returns void, if no `msg` given. Otherwise, returns promise call to * `nft_on_approve`, which can resolve with whatever it wants. */ nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; account_id: AccountId; msg?: string; }): Option; /** Revoke an approved account for a specific token. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - If contract requires >1yN deposit on `nft_approve`, contract * MUST refund associated storage deposit when owner revokes approval * - Contract MUST panic if called by someone other than token owner * * @param token_id - The token for which to revoke an approval * @param account_id - The account to remove from `approvals` */ nft_revoke({ token_id, account_id, }: { token_id: TokenId; account_id: AccountId; }): any; /** Revoke all approved accounts for a specific token. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - If contract requires >1yN deposit on `nft_approve`, contract * MUST refund all associated storage deposit when owner revokes approvals * - Contract MUST panic if called by someone other than token owner * * @param token_id - The token with approvals to revoke */ nft_revoke_all({ token_id }: { token_id: TokenId; }): any; /** Check if a token is approved for transfer by a given account, optionally * checking an approval_id * * @param token_id - The token for which to revoke an approval * @param approved_account_id - The account to check the existence of in `approvals` * @param approval_id - An optional approval ID to check against current approval ID for given account * @returns if `approval_id` given, `true` if `approved_account_id` is approved with given `approval_id` * otherwise, `true` if `approved_account_id` is in list of approved accounts */ nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; approval_id?: bigint; }): boolean; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/approval/index.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/index.d.ts ================================================ import { AccountId } from "near-sdk-js"; import { Token, TokenId } from "../token"; import { Option } from "../utils"; /** Used for all non-fungible tokens. The specification for the * [core non-fungible token standard] lays out the reasoning for each method. * It's important to check out [NonFungibleTokenReceiver](./receiver.ts) * and [NonFungibleTokenResolver](./resolver.ts) to * understand how the cross-contract call work. * * [core non-fungible token standard]: * * # Examples * * ```typescript * import { AccountId } from "near-sdk-js"; * import { Option } from "../non_fungible_token/utils"; * import { Token, TokenId } from "../token"; * import { NonFungibleTokenCore, NonFungibleToken } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements NonFungibleTokenCore { * private token: NonFungibleToken; * * constructor() { * this.tokens = new NonFungibleToken(); * } * * @call({}) * nft_transfer({ receiver_id, token_id, approval_id, memo }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }): any { * this.tokens.nft_transfer({ receiver_id, token_id, approval_id, memo }); * } * * @call({}) * nft_transfer_call({ receiver_id, token_id, approval_id, memo }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }): any { * return this.tokens.nft_transfer_call({ receiver_id, token_id, approval_id, memo }); * } * * @view({}) * nft_token({ token_id }: { token_id: TokenId; }): Option { return this.tokens.nft_token({ token_id }); }; * } * ``` */ export interface NonFungibleTokenCore { /** Simple transfer. Transfer a given `token_id` from current owner to * `receiver_id`. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes * - Contract MUST panic if called by someone other than token owner or, * if using Approval Management, one of the approved accounts * - `approval_id` is for use with Approval Management, * see * - If using Approval Management, contract MUST nullify approved accounts on * successful transfer. * - TODO: needed? Both accounts must be registered with the contract for transfer to * succeed. See see * * @param receiver_id - The valid NEAR account receiving the token * @param token_id - The token to transfer * @param approval_id - Expected approval ID. A number smaller than * 2^53, and therefore representable as JSON. See Approval Management * standard for full explanation. * @param memo (optional) - For use cases that may benefit from indexing or * providing information for a transfer */ nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }): any; /** Transfer token and call a method on a receiver contract. A successful * workflow will end in a success execution outcome to the callback on the NFT * contract at the method `nft_resolve_transfer`. * * You can think of this as being similar to attaching native NEAR tokens to a * function call. It allows you to attach any Non-Fungible Token in a call to a * receiver contract. * * Requirements: * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - Contract MUST panic if called by someone other than token owner or, * if using Approval Management, one of the approved accounts * - The receiving contract must implement `ft_on_transfer` according to the * standard. If it does not, FT contract's `ft_resolve_transfer` MUST deal * with the resulting failed cross-contract call and roll back the transfer. * - Contract MUST implement the behavior described in `ft_resolve_transfer` * - `approval_id` is for use with Approval Management extension, see * that document for full explanation. * - If using Approval Management, contract MUST nullify approved accounts on * successful transfer. * * @param receiver_id - The valid NEAR account receiving the token. * @param token_id - The token to send. * @param approval_id - Expected approval ID. A number smaller than * 2^53, and therefore representable as JSON. See Approval Management * standard for full explanation. * @param memo (optional) - For use cases that may benefit from indexing or * providing information for a transfer. * @param msg - Specifies information needed by the receiving contract in * order to properly handle the transfer. Can indicate both a function to * call and the parameters to pass to that function. */ nft_transfer_call({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }): any; /** Returns the token with the given `token_id` or `null` if no such token. */ nft_token({ token_id }: { token_id: TokenId; }): Option; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/index.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/receiver.d.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; import { TokenId } from "../token"; /** Used when an NFT is transferred using `nft_transfer_call`. This interface is implemented on the receiving contract, not on the NFT contract. * * # Examples * * ```typescript * import { AccountId, PromiseOrValue } from "near-sdk-js"; * import { TokenId } from "../token"; * import { NonFungibleTokenCore, NonFungibleToken, NonFungibleTokenReceiver } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements NonFungibleTokenCore, NonFungibleTokenReceiver { * private token: NonFungibleToken; * * constructor() { * this.tokens = new NonFungibleToken(); * } * * @call({}) * nft_on_transfer({ sender_id, previous_owner_id, token_id, msg, }: { sender_id: AccountId; previous_owner_id: AccountId; token_id: TokenId; msg: string; }): PromiseOrValue { return this.tokens.nft_on_transfer({ sender_id, previous_owner_id, token_id, msg }) } * } * ``` */ export interface NonFungibleTokenReceiver { /** Take some action after receiving a non-fungible token * * Requirements: * - Contract MUST restrict calls to this function to a set of whitelisted NFT * contracts * * @param sender_id - The sender of `nft_transfer_call` * @param previous_owner_id - The account that owned the NFT prior to it being * transferred to this contract, which can differ from `sender_id` if using * Approval Management extension * @param token_id - The `token_id` argument given to `nft_transfer_call` * @param msg - Information necessary for this contract to know how to process the * request. This may include method names and/or arguments. * * @returns true if token should be returned to `sender_id` */ nft_on_transfer({ sender_id, previous_owner_id, token_id, msg, }: { sender_id: AccountId; previous_owner_id: AccountId; token_id: TokenId; msg: string; }): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/receiver.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/resolver.d.ts ================================================ import { AccountId } from "near-sdk-js"; import { TokenId } from "../token"; /** Used when an NFT is transferred using `nft_transfer_call`. This is the method that's called after `nft_on_transfer`. This interface is implemented on the NFT contract. * * # Examples * * ```typescript * import { AccountId } from "near-sdk-js"; * import { TokenId } from "../token"; * import { NonFungibleTokenCore, NonFungibleToken, NonFungibleTokenResolver } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements NonFungibleTokenCore, NonFungibleTokenResolver { * private token: NonFungibleToken; * * constructor() { * this.tokens = new NonFungibleToken(); * } * * @call({}) * nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approval: AccountId]: bigint; }; }): boolean; { return this.tokens.nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }); } * } * ``` */ export interface NonFungibleTokenResolver { /** Finalize an `nft_transfer_call` chain of cross-contract calls. * * The `nft_transfer_call` process: * * 1. Sender calls `nft_transfer_call` on FT contract * 2. NFT contract transfers token from sender to receiver * 3. NFT contract calls `nft_on_transfer` on receiver contract * 4+. [receiver contract may make other cross-contract calls] * N. NFT contract resolves promise chain with `nft_resolve_transfer`, and may * transfer token back to sender * * Requirements: * - Contract MUST forbid calls to this function by any account except self * - If promise chain failed, contract MUST revert token transfer * - If promise chain resolves with `true`, contract MUST return token to * `sender_id` * * @param previous_owner_id - The owner prior to the call to `nft_transfer_call` * @param receiver_id - The `receiver_id` argument given to `nft_transfer_call` * @param token_id - The `token_id` argument given to `ft_transfer_call` * @param approved_account_ids - If using Approval Management, contract MUST provide * set of original approved accounts in this argument, and restore these * approved accounts in case of revert. * * @returns true if token was successfully transferred to `receiver_id`. */ nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approval: AccountId]: bigint; }; }): boolean; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/core/resolver.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/enumeration/index.d.ts ================================================ import { AccountId } from "near-sdk-js"; import { Token } from "../token"; /** Offers methods helpful in determining account ownership of NFTs and provides a way to page through NFTs per owner, determine total supply, etc. */ export interface NonFungibleTokenEnumeration { /** Returns the total supply of non-fungible tokens */ nft_total_supply(): number; /** Get a list of all tokens * * @param from_index - The starting index of tokens to return * @param limit - The maximum number of tokens to return * @returns - An array of Token objects, as described in Core standard */ nft_tokens({ from_index, limit, }: { from_index?: number; limit?: number; }): Token[]; /** Get number of tokens owned by a given account * * @param account_id - A valid NEAR account * @returns - The number of non-fungible tokens owned by given `account_id` */ nft_supply_for_owner({ account_id }: { account_id: AccountId; }): number; /** Get list of all tokens owned by a given account * * @param account_id - A valid NEAR account * @param from_index - The starting index of tokens to return * @param limit - The maximum number of tokens to return * @returns - A paginated list of all tokens owned by this account */ nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; from_index?: number; limit?: number; }): Token[]; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/enumeration/index.js ================================================ export {}; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/events.d.ts ================================================ /** Standard for nep171 (Non-Fungible Token) events. * * These events will be picked up by the NEAR indexer. * * * * This is an extension of the events format (nep-297): * * * The three events in this standard are [`NftMint`], [`NftTransfer`], and [`NftBurn`]. * * These events can be logged by calling `.emit()` on them if a single event, or calling * [`NftMint.emit_many`], [`NftTransfer.emit_many`], * or [`NftBurn.emit_many`] respectively. */ import { AccountId } from "near-sdk-js"; import { NearEvent } from "../event"; import { TokenId } from "./token"; export declare type Nep171EventKind = NftMint[] | NftTransfer[] | NftBurn[] | NftContractMetadataUpdate[]; export declare class Nep171Event extends NearEvent { standard: string; version: string; event: string; data: Nep171EventKind; constructor(version: string, event_kind: Nep171EventKind); } /** Data to log for an NFT mint event. To log this event, call `.emit()` */ export declare class NftMint { owner_id: AccountId; token_ids: TokenId[]; memo?: string; constructor(owner_id: AccountId, token_ids: TokenId[], memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an nft mint event, through `near.log`, * where each [`NftMint`] represents the data of each mint. */ static emit_many(data: NftMint[]): void; } /** Data to log for an NFT transfer event. To log this event, * call [`.emit()`](NftTransfer.emit). */ export declare class NftTransfer { old_owner_id: AccountId; new_owner_id: AccountId; token_ids: TokenId[]; authorized_id?: AccountId; memo?: string; constructor(old_owner_id: AccountId, new_owner_id: AccountId, token_ids: TokenId[], authorized_id?: AccountId, memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an nft transfer event, through `near.log`, * where each [`NftTransfer`] represents the data of each transfer. */ static emit_many(data: NftTransfer[]): void; } /** Data to log for an NFT burn event. To log this event, call [`.emit()`](NftBurn.emit). */ export declare class NftBurn { owner_id: AccountId; token_ids: TokenId[]; authorized_id?: string; memo?: string; constructor(owner_id: AccountId, token_ids: TokenId[], authorized_id?: string, memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an nft burn event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data: NftBurn[]): void; } /** Data to log for an NFT contract metadata updates. To log this event, call [`.emit()`](NftContractMetadataUpdate.emit). */ export declare class NftContractMetadataUpdate { memo?: string; constructor(memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; /** Emits an contract metadata update event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data: NftContractMetadataUpdate[]): void; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/events.js ================================================ import { NearEvent } from "../event"; import { toSnakeCase } from "../util"; export class Nep171Event extends NearEvent { constructor(version, event_kind) { super(); this.standard = "nep171"; this.version = version; this.event = toSnakeCase(event_kind[0].constructor.name); this.data = event_kind; } } /** Data to log for an NFT mint event. To log this event, call `.emit()` */ export class NftMint { constructor(owner_id, token_ids, memo) { this.owner_id = owner_id; this.token_ids = token_ids; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftMint.emit_many([this]); } /** Emits an nft mint event, through `near.log`, * where each [`NftMint`] represents the data of each mint. */ static emit_many(data) { new_171_v1(data).emit(); } } /** Data to log for an NFT transfer event. To log this event, * call [`.emit()`](NftTransfer.emit). */ export class NftTransfer { constructor(old_owner_id, new_owner_id, token_ids, authorized_id, memo) { this.old_owner_id = old_owner_id; this.new_owner_id = new_owner_id; this.token_ids = token_ids; this.authorized_id = authorized_id; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftTransfer.emit_many([this]); } /** Emits an nft transfer event, through `near.log`, * where each [`NftTransfer`] represents the data of each transfer. */ static emit_many(data) { new_171_v1(data).emit(); } } /** Data to log for an NFT burn event. To log this event, call [`.emit()`](NftBurn.emit). */ export class NftBurn { constructor(owner_id, token_ids, authorized_id, memo) { this.owner_id = owner_id; this.token_ids = token_ids; this.authorized_id = authorized_id; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftBurn.emit_many([this]); } /** Emits an nft burn event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data) { new_171_v1(data).emit(); } } /** Data to log for an NFT contract metadata updates. To log this event, call [`.emit()`](NftContractMetadataUpdate.emit). */ export class NftContractMetadataUpdate { constructor(memo) { this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftContractMetadataUpdate.emit_many([this]); } /** Emits an contract metadata update event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data) { new_171_v1(data).emit(); } } function new_171(version, event_kind) { return new Nep171Event(version, event_kind); } function new_171_v1(event_kind) { return new_171("1.0.0", event_kind); } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/impl.d.ts ================================================ import { AccountId, UnorderedMap, LookupMap, UnorderedSet, NearPromise, IntoStorageKey } from "near-sdk-js"; import { TokenMetadata } from "./metadata"; import { Option } from "./utils"; import { NonFungibleTokenResolver } from "./core/resolver"; import { Token, TokenId } from "./token"; import { NonFungibleTokenCore } from "./core"; import { NonFungibleTokenApproval } from "./approval"; import { NonFungibleTokenEnumeration } from "./enumeration"; /** Implementation of the non-fungible token standard. * Allows to include NEP-171 compatible token to any contract. * There are next interfaces that any contract may implement: * - NonFungibleTokenCore -- interface with nft_transfer methods. NonFungibleToken provides methods for it. * - NonFungibleTokenApproval -- interface with nft_approve methods. NonFungibleToken provides methods for it. * - NonFungibleTokenEnumeration -- interface for getting lists of tokens. NonFungibleToken provides methods for it. * - NonFungibleTokenMetadata -- return metadata for the token in NEP-177, up to contract to implement. * * For example usage, see near-contract-standards/example-contracts/non-fungible-token/my-nft.ts. */ export declare class NonFungibleToken implements NonFungibleTokenCore, NonFungibleTokenResolver, NonFungibleTokenApproval, NonFungibleTokenEnumeration { owner_id: AccountId; extra_storage_in_bytes_per_token: bigint; owner_by_id: UnorderedMap; token_metadata_by_id: Option>; tokens_per_owner: Option>>; approvals_by_id: Option>; next_approval_id_by_id: Option>; constructor(); nft_total_supply(): number; private enum_get_token; nft_tokens({ from_index, limit, }: { from_index?: number; limit?: number; }): Token[]; nft_supply_for_owner({ account_id }: { account_id: AccountId; }): number; nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; from_index?: number; limit?: number; }): Token[]; nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; account_id: AccountId; msg: string; }): Option; nft_revoke({ token_id, account_id, }: { token_id: TokenId; account_id: AccountId; }): void; nft_revoke_all({ token_id }: { token_id: TokenId; }): void; nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; approval_id?: bigint; }): boolean; init(owner_by_id_prefix: IntoStorageKey, owner_id: AccountId, token_metadata_prefix?: IntoStorageKey, enumeration_prefix?: IntoStorageKey, approval_prefix?: IntoStorageKey): void; static reconstruct(data: NonFungibleToken): NonFungibleToken; measure_min_token_storage_cost(): void; internal_transfer_unguarded(token_id: TokenId, from: AccountId, to: AccountId): void; internal_transfer(sender_id: AccountId, receiver_id: AccountId, token_id: TokenId, approval_id?: bigint, memo?: string): [AccountId, Option<{ [approvals: AccountId]: bigint; }>]; static emit_transfer(owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, sender_id?: AccountId, memo?: string): void; internal_mint(token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata): Token; internal_mint_with_refund(token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata, refund_id?: string): Token; nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }): void; nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }): NearPromise; nft_token({ token_id }: { token_id: TokenId; }): Option; nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approvals: AccountId]: bigint; }; }): boolean; } export declare type StorageKey = TokensPerOwner | TokenPerOwnerInner; export declare class TokensPerOwner implements IntoStorageKey { account_hash: Uint8Array; constructor(account_hash: Uint8Array); into_storage_key(): string; } export declare class TokenPerOwnerInner implements IntoStorageKey { account_id_hash: Uint8Array; constructor(account_id_hash: Uint8Array); into_storage_key(): string; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/impl.js ================================================ import { UnorderedMap, LookupMap, near, UnorderedSet, assert, NearPromise, bytes, serialize, str, } from "near-sdk-js"; import { TokenMetadata } from "./metadata"; import { refund_storage_deposit, refund_deposit, refund_deposit_to_account, assert_at_least_one_yocto, assert_one_yocto, } from "./utils"; import { NftMint, NftTransfer } from "./events"; import { Token } from "./token"; const GAS_FOR_RESOLVE_TRANSFER = 16000000000000n; const GAS_FOR_NFT_TRANSFER_CALL = 30000000000000n + GAS_FOR_RESOLVE_TRANSFER; const GAS_FOR_NFT_APPROVE = 21000000000000n; function repeat(str, n) { return Array(n + 1).join(str); } function expect_token_found(option) { if (option === null) { throw new Error("Token not found"); } return option; } function expect_approval(option) { if (option === null) { throw new Error("next_approval_by_id must be set for approval ext"); } return option; } /** Implementation of the non-fungible token standard. * Allows to include NEP-171 compatible token to any contract. * There are next interfaces that any contract may implement: * - NonFungibleTokenCore -- interface with nft_transfer methods. NonFungibleToken provides methods for it. * - NonFungibleTokenApproval -- interface with nft_approve methods. NonFungibleToken provides methods for it. * - NonFungibleTokenEnumeration -- interface for getting lists of tokens. NonFungibleToken provides methods for it. * - NonFungibleTokenMetadata -- return metadata for the token in NEP-177, up to contract to implement. * * For example usage, see near-contract-standards/example-contracts/non-fungible-token/my-nft.ts. */ export class NonFungibleToken { constructor() { this.owner_id = ""; this.extra_storage_in_bytes_per_token = 0n; this.owner_by_id = new UnorderedMap(""); this.token_metadata_by_id = null; this.tokens_per_owner = null; this.approvals_by_id = null; this.next_approval_id_by_id = null; } nft_total_supply() { return this.owner_by_id.length; } enum_get_token(owner_id, token_id) { const metadata = this.token_metadata_by_id.get(token_id, { reconstructor: TokenMetadata.reconstruct, }); const approved_account_ids = this.approvals_by_id.get(token_id, { defaultValue: {}, }); return new Token(token_id, owner_id, metadata, approved_account_ids); } nft_tokens({ from_index, limit, }) { const start_index = from_index === undefined ? 0 : from_index; assert(this.owner_by_id.length >= start_index, "Out of bounds, please use a smaller from_index."); let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, this.owner_by_id.length - start_index); const ret = []; const tokens = this.owner_by_id.keys({ start: from_index, limit: l }); for (let token_id of tokens) { const owner_id = this.owner_by_id.get(token_id); ret.push(this.enum_get_token(owner_id, token_id)); } return ret; } nft_supply_for_owner({ account_id }) { const tokens_per_owner = this.tokens_per_owner; assert(tokens_per_owner !== null, "Could not find tokens_per_owner when calling a method on the enumeration standard."); const account_tokens = tokens_per_owner.get(account_id, { reconstructor: UnorderedSet.reconstruct, }); return account_tokens === null ? 0 : account_tokens.length; } nft_tokens_for_owner({ account_id, from_index, limit, }) { const tokens_per_owner = this.tokens_per_owner; assert(tokens_per_owner !== undefined, "Could not find tokens_per_owner when calling a method on the enumeration standard."); const token_set = tokens_per_owner.get(account_id, { reconstructor: UnorderedSet.reconstruct, }); assert(token_set !== null, "Token set is empty"); const start_index = from_index === undefined ? 0 : from_index; assert(token_set.length >= start_index, "Out of bounds, please use a smaller from_index."); let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, token_set.length - start_index); const ret = []; const tokens = token_set.elements({ start: from_index, limit: l }); for (let token_id of tokens) { const owner_id = this.owner_by_id.get(token_id); ret.push(this.enum_get_token(owner_id, token_id)); } return ret; } nft_approve({ token_id, account_id, msg, }) { assert_at_least_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); assert(near.predecessorAccountId() === owner_id, "Predecessor must be token owner."); const next_approval_id_by_id = expect_approval(this.next_approval_id_by_id); const approved_account_ids = approvals_by_id.get(token_id) ?? {}; const approval_id = next_approval_id_by_id.get(token_id) ?? 1n; const old_approved_account_ids_size = serialize(approved_account_ids).length; approved_account_ids[account_id] = approval_id; const new_approved_account_ids_size = serialize(approved_account_ids).length; approvals_by_id.set(token_id, approved_account_ids); next_approval_id_by_id.set(token_id, approval_id + 1n); const storage_used = new_approved_account_ids_size - old_approved_account_ids_size; refund_deposit(BigInt(storage_used)); if (msg) { return NearPromise.new(account_id).functionCallRaw("nft_on_approve", serialize({ token_id, owner_id, approval_id, msg }), 0n, near.prepaidGas() - GAS_FOR_NFT_APPROVE); } return null; } nft_revoke({ token_id, account_id, }) { assert_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); const predecessorAccountId = near.predecessorAccountId(); assert(predecessorAccountId === owner_id, "Predecessor must be token owner."); const approved_account_ids = approvals_by_id.get(token_id); const old_approved_account_ids_size = serialize(approved_account_ids).length; let new_approved_account_ids_size; if (approved_account_ids[account_id]) { delete approved_account_ids[account_id]; if (Object.keys(approved_account_ids).length === 0) { approvals_by_id.remove(token_id); new_approved_account_ids_size = serialize(approved_account_ids).length; } else { approvals_by_id.set(token_id, approved_account_ids); new_approved_account_ids_size = 0; } refund_storage_deposit(predecessorAccountId, new_approved_account_ids_size - old_approved_account_ids_size); } } nft_revoke_all({ token_id }) { assert_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); const predecessorAccountId = near.predecessorAccountId(); assert(predecessorAccountId === owner_id, "Predecessor must be token owner."); const approved_account_ids = approvals_by_id.get(token_id); if (approved_account_ids) { refund_storage_deposit(predecessorAccountId, serialize(approved_account_ids).length); approvals_by_id.remove(token_id); } } nft_is_approved({ token_id, approved_account_id, approval_id, }) { expect_token_found(this.owner_by_id.get(token_id)); if (this.approvals_by_id === null) { return false; } const approvals_by_id = this.approvals_by_id; const approved_account_ids = approvals_by_id.get(token_id); if (approved_account_ids === null) { return false; } const actual_approval_id = approved_account_ids[approved_account_id]; if (actual_approval_id === undefined) { return false; } if (approval_id) { return BigInt(approval_id) === actual_approval_id; } return true; } init(owner_by_id_prefix, owner_id, token_metadata_prefix, enumeration_prefix, approval_prefix) { let approvals_by_id; let next_approval_id_by_id; if (approval_prefix) { const prefix = approval_prefix.into_storage_key(); approvals_by_id = new LookupMap(prefix); next_approval_id_by_id = new LookupMap(prefix + "n"); } else { approvals_by_id = null; next_approval_id_by_id = null; } this.owner_id = owner_id; this.extra_storage_in_bytes_per_token = 0n; this.owner_by_id = new UnorderedMap(owner_by_id_prefix.into_storage_key()); this.token_metadata_by_id = token_metadata_prefix ? new LookupMap(token_metadata_prefix.into_storage_key()) : null; this.tokens_per_owner = enumeration_prefix ? new LookupMap(enumeration_prefix.into_storage_key()) : null; this.approvals_by_id = approvals_by_id; this.next_approval_id_by_id = next_approval_id_by_id; this.measure_min_token_storage_cost(); } static reconstruct(data) { const ret = new NonFungibleToken(); Object.assign(ret, data); ret.owner_by_id = UnorderedMap.reconstruct(ret.owner_by_id); if (ret.token_metadata_by_id) { ret.token_metadata_by_id = LookupMap.reconstruct(ret.token_metadata_by_id); } if (ret.tokens_per_owner) { ret.tokens_per_owner = LookupMap.reconstruct(ret.tokens_per_owner); } if (ret.approvals_by_id) { ret.approvals_by_id = LookupMap.reconstruct(ret.approvals_by_id); } if (ret.next_approval_id_by_id) { ret.next_approval_id_by_id = LookupMap.reconstruct(ret.next_approval_id_by_id); } return ret; } measure_min_token_storage_cost() { const initial_storage_usage = near.storageUsage(); // 64 Length because this is the max account id length const tmp_token_id = repeat("a", 64); const tmp_owner_id = repeat("a", 64); // 1. set some dummy data this.owner_by_id.set(tmp_token_id, tmp_owner_id); if (this.token_metadata_by_id) { this.token_metadata_by_id.set(tmp_token_id, new TokenMetadata(repeat("a", 64), repeat("a", 64), repeat("a", 64), repeat("a", 64), 1n, null, null, null, null, null, null, null)); } if (this.tokens_per_owner) { const u = new UnorderedSet(new TokensPerOwner(near.sha256(bytes(tmp_owner_id))).into_storage_key()); u.set(tmp_token_id); this.tokens_per_owner.set(tmp_owner_id, u); } if (this.approvals_by_id) { const approvals = {}; approvals[tmp_owner_id] = 1n; this.approvals_by_id.set(tmp_token_id, approvals); } if (this.next_approval_id_by_id) { this.next_approval_id_by_id.set(tmp_token_id, 1n); } // 2. see how much space it took this.extra_storage_in_bytes_per_token = near.storageUsage() - initial_storage_usage; // 3. roll it all back if (this.next_approval_id_by_id) { this.next_approval_id_by_id.remove(tmp_token_id); } if (this.approvals_by_id) { this.approvals_by_id.remove(tmp_token_id); } if (this.tokens_per_owner) { const u = this.tokens_per_owner.remove(tmp_owner_id, { reconstructor: UnorderedSet.reconstruct, }); u.remove(tmp_token_id); } if (this.token_metadata_by_id) { this.token_metadata_by_id.remove(tmp_token_id); } this.owner_by_id.remove(tmp_token_id); } internal_transfer_unguarded(token_id, from, to) { this.owner_by_id.set(token_id, to); if (this.tokens_per_owner) { const owner_tokens_set = this.tokens_per_owner.get(from, { reconstructor: UnorderedSet.reconstruct, }); if (owner_tokens_set == null) { throw new Error("Unable to access tokens per owner in unguarded call."); } owner_tokens_set.remove(token_id); if (owner_tokens_set.isEmpty()) { this.tokens_per_owner.remove(from); } else { this.tokens_per_owner.set(from, owner_tokens_set); } let receiver_tokens_set = this.tokens_per_owner.get(to, { reconstructor: UnorderedSet.reconstruct, }); if (receiver_tokens_set === null) { receiver_tokens_set = new UnorderedSet(new TokensPerOwner(near.sha256(bytes(to))).into_storage_key()); } receiver_tokens_set.set(token_id); this.tokens_per_owner.set(to, receiver_tokens_set); } } internal_transfer(sender_id, receiver_id, token_id, approval_id, memo) { const owner_id = this.owner_by_id.get(token_id); if (owner_id == null) { throw new Error("Token not found"); } const approved_account_ids = this.approvals_by_id?.remove(token_id); let sender_id_authorized; if (sender_id != owner_id) { if (!approved_account_ids) { throw new Error("Unauthorized"); } const actual_approval_id = approved_account_ids[sender_id]; if (!actual_approval_id) { throw new Error("Sender not approved"); } assert(approval_id === undefined || approval_id == actual_approval_id, `The actual approval_id ${actual_approval_id} is different from the given ${approval_id}`); sender_id_authorized = sender_id; } else { sender_id_authorized = undefined; } assert(owner_id != receiver_id, "Current and next owner must differ"); this.internal_transfer_unguarded(token_id, owner_id, receiver_id); NonFungibleToken.emit_transfer(owner_id, receiver_id, token_id, sender_id_authorized, memo); return [owner_id, approved_account_ids]; } static emit_transfer(owner_id, receiver_id, token_id, sender_id, memo) { new NftTransfer(owner_id, receiver_id, [token_id], sender_id && sender_id == owner_id ? sender_id : undefined, memo).emit(); } internal_mint(token_id, token_owner_id, token_metadata) { const token = this.internal_mint_with_refund(token_id, token_owner_id, token_metadata, near.predecessorAccountId()); new NftMint(token.owner_id, [token.token_id]).emit(); return token; } internal_mint_with_refund(token_id, token_owner_id, token_metadata, refund_id) { let initial_storage_usage = null; if (refund_id) { initial_storage_usage = [refund_id, near.storageUsage()]; } if (this.token_metadata_by_id && token_metadata === undefined) { throw new Error("Must provide metadata"); } if (this.owner_by_id.get(token_id)) { throw new Error("token_id must be unique"); } const owner_id = token_owner_id; this.owner_by_id.set(token_id, owner_id); this.token_metadata_by_id?.set(token_id, token_metadata); if (this.tokens_per_owner) { let token_ids = this.tokens_per_owner.get(owner_id, { reconstructor: UnorderedSet.reconstruct, }); if (token_ids === null) { token_ids = new UnorderedSet(new TokensPerOwner(near.sha256(bytes(owner_id))).into_storage_key()); } token_ids.set(token_id); this.tokens_per_owner.set(owner_id, token_ids); } const approved_account_ids = this.approvals_by_id ? {} : undefined; if (initial_storage_usage) { const [id, storage_usage] = initial_storage_usage; refund_deposit_to_account(near.storageUsage() - storage_usage, id); } return new Token(token_id, owner_id, token_metadata, approved_account_ids); } nft_transfer({ receiver_id, token_id, approval_id, memo, }) { assert_at_least_one_yocto(); const sender_id = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, token_id, approval_id, memo); } nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }) { assert_at_least_one_yocto(); assert(near.prepaidGas() > GAS_FOR_NFT_TRANSFER_CALL, "Not enough prepaid gas"); const sender_id = near.predecessorAccountId(); const [previous_owner_id, approved_account_ids] = this.internal_transfer(sender_id, receiver_id, token_id, approval_id, memo); const promise = NearPromise.new(receiver_id) .functionCall("nft_on_transfer", JSON.stringify({ sender_id, previous_owner_id, token_id, msg }), 0n, near.prepaidGas() - GAS_FOR_NFT_TRANSFER_CALL) .then(NearPromise.new(near.currentAccountId()).functionCall("nft_resolve_transfer", JSON.stringify({ previous_owner_id, receiver_id, token_id, approved_account_ids, }), 0n, GAS_FOR_RESOLVE_TRANSFER)); return promise; } nft_token({ token_id }) { const owner_id = this.owner_by_id.get(token_id); if (owner_id == null) { return null; } const metadata = this.token_metadata_by_id?.get(token_id, { reconstructor: TokenMetadata.reconstruct, }); const approved_account_ids = this.approvals_by_id?.get(token_id); return new Token(token_id, owner_id, metadata, approved_account_ids); } nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }) { let must_revert = false; let p; try { p = near.promiseResult(0); } catch (e) { if (e.message.includes("Not Ready")) { throw new Error(); } else { must_revert = true; } } if (!must_revert) { try { const yes_or_no = JSON.parse(p); if (typeof yes_or_no == "boolean") { must_revert = yes_or_no; } else { must_revert = true; } } catch (_e) { must_revert = true; } } if (!must_revert) { return true; } const current_owner = this.owner_by_id.get(token_id); if (current_owner) { if (current_owner != receiver_id) { return true; } } else { if (approved_account_ids) { refund_storage_deposit(previous_owner_id, serialize(approved_account_ids).length); } return true; } this.internal_transfer_unguarded(token_id, receiver_id, previous_owner_id); if (this.approvals_by_id) { const receiver_approvals = this.approvals_by_id.get(token_id); if (receiver_approvals) { refund_storage_deposit(receiver_id, serialize(receiver_approvals).length); } if (approved_account_ids) { this.approvals_by_id.set(token_id, approved_account_ids); } } NonFungibleToken.emit_transfer(receiver_id, previous_owner_id, token_id, null, null); return false; } } export class TokensPerOwner { constructor(account_hash) { this.account_hash = account_hash; } into_storage_key() { return "\x00" + str(this.account_hash); } } export class TokenPerOwnerInner { constructor(account_id_hash) { this.account_id_hash = account_id_hash; } into_storage_key() { return "\x01" + str(this.account_id_hash); } } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/index.d.ts ================================================ /** The [approval management standard](https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html) for NFTs. */ export * from "./approval"; /** The [core non-fungible token standard](https://nomicon.io/Standards/NonFungibleToken/Core.html). This can be though of as the base standard, with the others being extension standards. */ export * from "./core"; /** Interface for the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Enumeration.html). * This provides useful view-only methods returning token supply, tokens by owner, etc. */ export * from "./enumeration"; export * from "./events"; export * from "./impl"; /** Metadata interfaces and implementation according to the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Metadata.html). * This covers both the contract metadata and the individual token metadata. */ export * from "./metadata"; /** The Token struct for the non-fungible token. */ export * from "./token"; /** NFT utility functions */ export * from "./utils"; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/index.js ================================================ /** The [approval management standard](https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html) for NFTs. */ export * from "./approval"; /** The [core non-fungible token standard](https://nomicon.io/Standards/NonFungibleToken/Core.html). This can be though of as the base standard, with the others being extension standards. */ export * from "./core"; /** Interface for the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Enumeration.html). * This provides useful view-only methods returning token supply, tokens by owner, etc. */ export * from "./enumeration"; export * from "./events"; export * from "./impl"; /** Metadata interfaces and implementation according to the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Metadata.html). * This covers both the contract metadata and the individual token metadata. */ export * from "./metadata"; /** The Token struct for the non-fungible token. */ export * from "./token"; /** NFT utility functions */ export * from "./utils"; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/metadata.d.ts ================================================ import { Option } from "./utils"; /** This spec can be treated like a version of the standard. */ export declare const NFT_METADATA_SPEC = "nft-1.0.0"; /** Metadata for the NFT contract itself. */ export declare class NFTContractMetadata { spec: string; name: string; symbol: string; icon: Option; base_uri: Option; reference: Option; reference_hash: Option; constructor(); init(spec: string, name: string, symbol: string, icon: Option, base_uri: Option, reference: Option, reference_hash: Option): void; assert_valid(): void; static reconstruct(data: NFTContractMetadata): NFTContractMetadata; } /** Metadata on the individual token level. */ export declare class TokenMetadata { title: Option; description: Option; media: Option; media_hash: Option; copies: Option; issued_at: Option; expires_at: Option; starts_at: Option; updated_at: Option; extra: Option; reference: Option; reference_hash: Option; constructor(title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" description: Option, // free-form description media: Option, // URL to associated media, preferably to decentralized, content-addressed storage media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. copies: Option, // number of copies of this set of metadata in existence when token was minted. issued_at: Option, // ISO 8601 datetime when token was issued or minted expires_at: Option, // ISO 8601 datetime when token expires starts_at: Option, // ISO 8601 datetime when token starts being valid updated_at: Option, // ISO 8601 datetime when token was last updated extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. reference: Option, // URL to an off-chain JSON file with more info. reference_hash: Option); assert_valid(): void; static reconstruct(data: TokenMetadata): TokenMetadata; } /** Offers details on the contract-level metadata. */ export interface NonFungibleTokenMetadataProvider { nft_metadata(): NFTContractMetadata; } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/metadata.js ================================================ import { assert } from "near-sdk-js"; /** This spec can be treated like a version of the standard. */ export const NFT_METADATA_SPEC = "nft-1.0.0"; /** Metadata for the NFT contract itself. */ export class NFTContractMetadata { constructor() { this.spec = NFT_METADATA_SPEC; this.name = ""; this.symbol = ""; this.icon = null; this.base_uri = null; this.reference = null; this.reference_hash = null; } init(spec, name, symbol, icon, base_uri, reference, reference_hash) { this.spec = spec; this.name = name; this.symbol = symbol; this.icon = icon; this.base_uri = base_uri; this.reference = reference; this.reference_hash = reference_hash; } assert_valid() { assert(this.spec == NFT_METADATA_SPEC, "Spec is not NFT metadata"); assert((this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present"); if (this.reference_hash != null) { assert(this.reference_hash.length == 32, "Hash has to be 32 bytes"); } } static reconstruct(data) { const metadata = new NFTContractMetadata(); Object.assign(metadata, data); return metadata; } } /** Metadata on the individual token level. */ export class TokenMetadata { constructor(title, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" description, // free-form description media, // URL to associated media, preferably to decentralized, content-addressed storage media_hash, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. copies, // number of copies of this set of metadata in existence when token was minted. issued_at, // ISO 8601 datetime when token was issued or minted expires_at, // ISO 8601 datetime when token expires starts_at, // ISO 8601 datetime when token starts being valid updated_at, // ISO 8601 datetime when token was last updated extra, // anything extra the NFT wants to store on-chain. Can be stringified JSON. reference, // URL to an off-chain JSON file with more info. reference_hash // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. ) { this.title = title; this.description = description; this.media = media; this.media_hash = media_hash; this.copies = copies; this.issued_at = issued_at; this.expires_at = expires_at; this.starts_at = starts_at; this.updated_at = updated_at; this.extra = extra; this.reference = reference; this.reference_hash = reference_hash; } assert_valid() { assert((this.media != null) == (this.media_hash != null), "Media and media hash must be present"); if (this.media_hash != null) { assert(this.media_hash.length == 32, "Media hash has to be 32 bytes"); } assert((this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present"); if (this.reference_hash != null) { assert(this.reference_hash.length == 32, "Reference hash has to be 32 bytes"); } } static reconstruct(data) { return new TokenMetadata(data.title, data.description, data.media, data.media_hash, data.copies, data.issued_at, data.expires_at, data.starts_at, data.updated_at, data.extra, data.reference, data.reference_hash); } } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/token.d.ts ================================================ import { TokenMetadata } from "./metadata"; import { AccountId } from "near-sdk-js"; /** Note that token IDs for NFTs are strings on NEAR. It's still fine to use auto incrementing numbers as unique IDs if desired, but they should be stringified. This is to make IDs more future-proof as chain-agnostic conventions and standards arise, and allows for more flexibility with considerations like bridging NFTs across chains, etc. */ export declare type TokenId = string; /** In this implementation, the Token struct takes two extensions standards (metadata and approval) as optional fields, as they are frequently used in modern NFTs. */ export declare class Token { token_id: TokenId; owner_id: AccountId; metadata?: TokenMetadata; approved_account_ids?: { [approved_account_id: AccountId]: bigint; }; constructor(token_id: TokenId, owner_id: AccountId, metadata?: TokenMetadata, approved_account_ids?: { [approved_account_id: AccountId]: bigint; }); } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/token.js ================================================ /** In this implementation, the Token struct takes two extensions standards (metadata and approval) as optional fields, as they are frequently used in modern NFTs. */ export class Token { constructor(token_id, owner_id, metadata, approved_account_ids) { this.token_id = token_id; this.owner_id = owner_id; this.metadata = metadata; this.approved_account_ids = approved_account_ids; } } ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/utils.d.ts ================================================ import { AccountId } from "near-sdk-js"; export declare function refund_storage_deposit(account_id: AccountId, storage_released: number): void; export declare function refund_deposit_to_account(storage_used: bigint, account_id: AccountId): void; /** Assumes that the predecessor will be refunded */ export declare function refund_deposit(storage_used: bigint): void; export declare function hash_account_id(account_id: AccountId): Uint8Array; /** Assert that at least 1 yoctoNEAR was attached. */ export declare function assert_at_least_one_yocto(): void; /** Assert that exactly 1 yoctoNEAR was attached */ export declare function assert_one_yocto(): void; export declare type Option = T | null; ================================================ FILE: packages/near-contract-standards/lib/non_fungible_token/utils.js ================================================ import { near, assert, bytes } from "near-sdk-js"; export function refund_storage_deposit(account_id, storage_released) { const promise_id = near.promiseBatchCreate(account_id); near.promiseBatchActionTransfer(promise_id, BigInt(storage_released) * near.storageByteCost()); near.promiseReturn(promise_id); } export function refund_deposit_to_account(storage_used, account_id) { const required_cost = near.storageByteCost() * storage_used; const attached_deposit = near.attachedDeposit(); assert(required_cost <= attached_deposit, `Must attach ${required_cost} yoctoNEAR to cover storage`); const refund = attached_deposit - required_cost; if (refund > 1n) { const promise_id = near.promiseBatchCreate(account_id); near.promiseBatchActionTransfer(promise_id, refund); near.promiseReturn(promise_id); } } /** Assumes that the predecessor will be refunded */ export function refund_deposit(storage_used) { refund_deposit_to_account(storage_used, near.predecessorAccountId()); } export function hash_account_id(account_id) { return near.sha256(bytes(account_id)); } /** Assert that at least 1 yoctoNEAR was attached. */ export function assert_at_least_one_yocto() { assert(near.attachedDeposit() >= 1n, "Requires attached deposit of at least 1 yoctoNEAR"); } /** Assert that exactly 1 yoctoNEAR was attached */ export function assert_one_yocto() { assert(near.attachedDeposit() === 1n, "Requires attached deposit of 1 yoctoNEAR"); } ================================================ FILE: packages/near-contract-standards/lib/storage_management/index.d.ts ================================================ import { AccountId, Balance } from "near-sdk-js"; import { Option } from "../non_fungible_token/utils"; export declare class StorageBalance { total: Balance; available: Balance; constructor(total: Balance, available: Balance); } export declare class StorageBalanceBounds { constructor(min: Balance, max: Option); min: Balance; max: Option; } export interface StorageManagement { /** * @param registration_only if `true` MUST refund above the minimum balance if the account didn't exist and * refund full deposit if the account exists. */ storage_deposit({ account_id, registration_only }: { account_id: Option; registration_only: Option; }): StorageBalance; /** Withdraw specified amount of available Ⓝ for predecessor account. * * This method is safe to call. It MUST NOT remove data. * * @param amount is sent as a string representing an unsigned 128-bit integer. If * omitted, contract MUST refund full `available` balance. If `amount` exceeds * predecessor account's available balance, contract MUST panic. * * If predecessor account not registered, contract MUST panic. * * MUST require exactly 1 yoctoNEAR attached balance to prevent restricted * function-call access-key call (UX wallet security) * * @returns the StorageBalance structure showing updated balances. */ storage_withdraw({ amount }: { amount?: bigint; }): StorageBalance; /** Unregisters the predecessor account and returns the storage NEAR deposit back. * * If the predecessor account is not registered, the function MUST return `false` without panic. * * @param force If `force=true` the function SHOULD ignore account balances (burn them) and close the account. * Otherwise, MUST panic if caller has a positive registered balance (eg token holdings) or * the contract doesn't support force unregistration. * MUST require exactly 1 yoctoNEAR attached balance to prevent restricted function-call access-key call * (UX wallet security) * @returns `true` if the account was unregistered, `false` if account was not registered before. */ storage_unregister({ force }: { force: Option; }): boolean; storage_balance_bounds(): StorageBalanceBounds; storage_balance_of({ account_id }: { account_id: AccountId; }): Option; } ================================================ FILE: packages/near-contract-standards/lib/storage_management/index.js ================================================ export class StorageBalance { constructor(total, available) { this.total = total; this.available = available; } } export class StorageBalanceBounds { constructor(min, max) { this.min = min; this.max = max; } } ================================================ FILE: packages/near-contract-standards/lib/util.d.ts ================================================ export declare const toSnakeCase: (str: string) => string; ================================================ FILE: packages/near-contract-standards/lib/util.js ================================================ export const toSnakeCase = (str) => { return str.replace(/[A-Z]/g, (letter, index) => { return index == 0 ? letter.toLowerCase() : '_' + letter.toLowerCase(); }); }; ================================================ FILE: packages/near-contract-standards/package.json ================================================ { "name": "near-contract-standards", "version": "2.0.0", "description": "Compatible near-contract-standards implementation in JS", "main": "index.js", "type": "module", "scripts": { "build": "tsc" }, "author": "Near Inc ", "license": "Apache-2.0", "dependencies": { "lodash-es": "4.17.21", "near-sdk-js": "workspace:*" }, "devDependencies": { "typescript": "4.7.4" }, "files": [ "lib" ] } ================================================ FILE: packages/near-contract-standards/src/event.ts ================================================ import { near } from "near-sdk-js"; export abstract class NearEvent { private internal_to_json_string(): string { return JSON.stringify(this); } private internal_to_json_event_string(): string { return `EVENT_JSON: ${this.internal_to_json_string()}`; } /** * Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void { near.log(this.internal_to_json_event_string()); } } ================================================ FILE: packages/near-contract-standards/src/fungible_token/core.ts ================================================ import { AccountId, PromiseOrValue, Balance } from "near-sdk-js" import { Option } from "../non_fungible_token/utils" /** * The core methods for a basic fungible token. Extension standards may be * added in addition to this interface. * * # Examples * * ```typescript * import { near, call, view, AccountId, PromiseOrValue, Balance } from "near-sdk-js"; * import { Option } from "../non_fungible_token/utils"; * import { FungibleTokenCore, FungibleToken } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({}) * ft_transfer({ receiver_id, amount, memo }: { * receiver_id: AccountId, * amount: Balance, * memo?: String * }): void { * this.token.ft_transfer({ receiver_id, amount, memo }); * } * * @call({}) * ft_transfer_call({ receiver_id, amount, memo, msg }: { * receiver_id: AccountId, * amount: Balance, * memo?: Option, * msg: String * }): PromiseOrValue { * return this.token.ft_transfer_call({ receiver_id, amount, memo, msg }); * } * * @view({}) * ft_total_supply(): Balance { * return this.token.ft_total_supply(); * } * * @view({}) * ft_balance_of({ account_id }: { account_id: AccountId }): Balance { * return this.token.ft_balance_of({ account_id }); * } * } * ``` */ export interface FungibleTokenCore { /** * Transfers positive `amount` of tokens from the `near.predecessorAccountId()` to `receiver_id`. * Both accounts must be registered with the contract for transfer to succeed. (See [NEP-145](https://github.com/near/NEPs/discussions/145)) * This method must to be able to accept attached deposits, and must not panic on attached deposit. * Exactly 1 yoctoNEAR must be attached. * See [the Security section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Arguments: * @param receiver_id - the account ID of the receiver. * @param amount - the amount of tokens to transfer. Must be a positive number in decimal string representation. * @param memo - an optional string field in a free form to associate a memo with this transfer. */ ft_transfer({ receiver_id, amount, memo }: { receiver_id: AccountId, amount: Balance, memo?: String }); /** * Transfers positive `amount` of tokens from the `near.predecessorAccountId()` to `receiver_id` account. Then * calls `ft_on_transfer` method on `receiver_id` contract and attaches a callback to resolve this transfer. * `ft_on_transfer` method must return the amount of tokens unused by the receiver contract, the remaining tokens * must be refunded to the `predecessor_account_id` at the resolve transfer callback. * * Token contract must pass all the remaining unused gas to the `ft_on_transfer` call. * * Malicious or invalid behavior by the receiver's contract: * - If the receiver contract promise fails or returns invalid value, the full transfer amount must be refunded. * - If the receiver contract overspent the tokens, and the `receiver_id` balance is lower than the required refund * amount, the remaining balance must be refunded. See [the Security section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Both accounts must be registered with the contract for transfer to succeed. (See #145) * This method must to be able to accept attached deposits, and must not panic on attached deposit. Exactly 1 yoctoNEAR must be attached. See [the Security * section](https://github.com/near/NEPs/issues/141#user-content-security) of the standard. * * Arguments: * @param receiver_id - the account ID of the receiver contract. This contract will be called. * @param amount - the amount of tokens to transfer. Must be a positive number in a decimal string representation. * @param memo - an optional string field in a free form to associate a memo with this transfer. * @param msg - a string message that will be passed to `ft_on_transfer` contract call. * * @returns a promise which will result in the amount of tokens withdrawn from sender's account. */ ft_transfer_call({ receiver_id, amount, memo, msg }: { receiver_id: AccountId, amount: Balance, memo: Option, msg: String }): PromiseOrValue; /** Returns the total supply of the token in a decimal string representation. */ ft_total_supply(): Balance; /** Returns the balance of the account. If the account doesn't exist must returns `"0"`. */ ft_balance_of({ account_id }: { account_id: AccountId }): Balance; } ================================================ FILE: packages/near-contract-standards/src/fungible_token/core_impl.ts ================================================ import { StorageBalance, StorageBalanceBounds, StorageManagement } from "../storage_management"; import { FungibleTokenCore } from "./core"; import { FtBurn, FtTransfer } from "./events"; import { FungibleTokenResolver } from "./resolver"; import { near, AccountId, LookupMap, Balance, Gas, PromiseOrValue, NearPromise, StorageUsage, assert, IntoStorageKey, } from "near-sdk-js"; import { Option } from '../non_fungible_token/utils'; // TODO: move to the main SDK package import { assert_one_yocto } from "../non_fungible_token/utils"; const GAS_FOR_RESOLVE_TRANSFER: Gas = 15_000_000_000_000n; const GAS_FOR_FT_TRANSFER_CALL: Gas = 25_000_000_000_000n + GAS_FOR_RESOLVE_TRANSFER; const ERR_TOTAL_SUPPLY_OVERFLOW: string = "Total supply overflow"; /** Implementation of a FungibleToken standard * Allows to include NEP-141 compatible token to any contract. * There are next interfaces that any contract may implement: * - FungibleTokenCore -- interface with ft_transfer methods. FungibleToken provides methods for it. * - FungibleTokenMetaData -- return metadata for the token in NEP-148, up to contract to implement. * - StorageManager -- interface for NEP-145 for allocating storage per account. FungibleToken provides methods for it. * - AccountRegistrar -- interface for an account to register and unregister * * For example usage, see examples/src/fungible-token/my-ft.ts */ export class FungibleToken implements FungibleTokenCore, StorageManagement, FungibleTokenResolver { // AccountID -> Account balance. accounts: LookupMap; // Total supply of the all token. total_supply: Balance; // The storage size in bytes for one account. account_storage_usage: StorageUsage; constructor() { this.accounts = new LookupMap(""); this.total_supply = 0n; this.account_storage_usage = 0n; } init(prefix: IntoStorageKey) { const storage_prefix = prefix.into_storage_key(); this.accounts = new LookupMap(storage_prefix); this.total_supply = 0n; this.account_storage_usage = 0n; this.measure_account_storage_usage(); return this; } measure_account_storage_usage() { let initial_storage_usage: bigint = near.storageUsage(); let tmp_account_id: string = "a".repeat(64); this.accounts.set(tmp_account_id, 0n); this.account_storage_usage = near.storageUsage() - initial_storage_usage; this.accounts.remove(tmp_account_id); } internal_unwrap_balance_of(account_id: AccountId): Balance { let balance = this.accounts.get(account_id); if (balance === null) { throw Error(`The account ${account_id} is not registered`); } return BigInt(balance); } internal_deposit(account_id: AccountId, amount: Balance) { let balance: Balance = BigInt(this.internal_unwrap_balance_of(account_id)); let new_balance: Balance = balance + amount; this.accounts.set(account_id, new_balance); let new_total_supply: Balance = this.total_supply + amount; this.total_supply = new_total_supply; } internal_withdraw(account_id: AccountId, amount: Balance) { let balance: Balance = BigInt(this.internal_unwrap_balance_of(account_id)); let new_balance: Balance = balance - amount; if (new_balance < 0) { throw Error("The account doesn't have enough balance"); } this.accounts.set(account_id, new_balance); let new_total_supply: Balance = this.total_supply - amount; this.total_supply = new_total_supply; } internal_transfer( sender_id: AccountId, receiver_id: AccountId, amount: Balance, memo?: String, ) { assert(sender_id != receiver_id, "Sender and receiver should be different"); assert(amount > 0, "The amount should be a positive number"); this.internal_withdraw(sender_id, amount); this.internal_deposit(receiver_id, amount); new FtTransfer(sender_id, receiver_id, amount, memo).emit(); } internal_register_account(account_id: AccountId) { if (this.accounts.containsKey(account_id)) { throw Error("The account is already registered"); } this.accounts.set(account_id, 0n); } /** Internal method that returns the amount of burned tokens in a corner case when the sender * has deleted (unregistered) their account while the `ft_transfer_call` was still in flight. * Returns (Used token amount, Burned token amount) */ internal_ft_resolve_transfer(sender_id: AccountId, receiver_id: AccountId, amount: Balance): [bigint, bigint] { // Get the unused amount from the `ft_on_transfer` call result. let unused_amount: Balance; try { const promise_result = near.promiseResult(0).replace(/"*/g, ''); //TODO: why promiseResult returns result with brackets? unused_amount = this.bigIntMin(amount, BigInt(promise_result)); } catch (e) { if (e.message.includes('Failed')) { unused_amount = amount; } else { throw e; } } if (unused_amount > 0) { let receiver_balance: Balance = BigInt(this.accounts.get(receiver_id) ?? 0); if (receiver_balance > 0n) { let refund_amount: Balance = this.bigIntMin(receiver_balance, unused_amount); let new_receiver_balance: Balance = receiver_balance - refund_amount; if (new_receiver_balance < 0n) { throw Error("The receiver account doesn't have enough balance"); } this.accounts.set(receiver_id, new_receiver_balance); let sender_balance = this.accounts.get(sender_id); if (sender_balance) { sender_balance = BigInt(sender_balance); let new_sender_balance: Balance = sender_balance + refund_amount; this.accounts.set(sender_id, new_sender_balance); new FtTransfer( receiver_id, sender_id, refund_amount, "refund", ).emit(); let used_amount: Balance = amount - refund_amount; if (used_amount < 0n) { throw Error(ERR_TOTAL_SUPPLY_OVERFLOW); } return [used_amount.valueOf(), 0n]; } else { const new_total_supply = this.total_supply - refund_amount; if (new_total_supply < 0) { throw Error(ERR_TOTAL_SUPPLY_OVERFLOW); } this.total_supply = new_total_supply near.log("The account of the sender was deleted"); new FtBurn( receiver_id, refund_amount, "refund", ).emit(); return [amount, refund_amount]; } } } return [amount, 0n]; } /** Implementation of FungibleTokenCore */ ft_transfer({ receiver_id, amount, memo }: { receiver_id: AccountId, amount: Balance, memo?: String }) { amount = BigInt(amount); assert_one_yocto(); let sender_id: AccountId = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, amount, memo); } ft_transfer_call({ receiver_id, amount, memo, msg }: { receiver_id: AccountId, amount: Balance, memo: Option, msg: string }): PromiseOrValue { amount = BigInt(amount); assert_one_yocto(); assert(near.prepaidGas() > GAS_FOR_FT_TRANSFER_CALL, "More gas is required"); let sender_id: AccountId = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, amount, memo); let receiver_gas: bigint = near.prepaidGas() - GAS_FOR_FT_TRANSFER_CALL; if (receiver_gas < 0) { throw new Error("Prepaid gas overflow"); } return NearPromise.new(receiver_id) .functionCall("ft_on_transfer", JSON.stringify({ sender_id, amount: String(amount), msg }), BigInt(0), receiver_gas) .then( NearPromise.new(near.currentAccountId()) .functionCall( "ft_resolve_transfer", JSON.stringify({ sender_id, receiver_id, amount: String(amount) }), BigInt(0), GAS_FOR_RESOLVE_TRANSFER ) ); } ft_total_supply(): Balance { return this.total_supply; } ft_balance_of({ account_id }: { account_id: AccountId }): Balance { return this.accounts.get(account_id) ?? 0n; } /** Implementation of storage * Internal method that returns the Account ID and the balance in case the account was * unregistered. */ internal_storage_unregister(force?: boolean): Option<[AccountId, Balance]> { assert_one_yocto(); let account_id: AccountId = near.predecessorAccountId(); let balance: Balance = BigInt(this.accounts.get(account_id)); if (balance || balance == 0n) { if (balance == 0n || force) { this.accounts.remove(account_id); this.total_supply = this.total_supply - balance; NearPromise.new(account_id).transfer(this.storage_balance_bounds().min + 1n); return [account_id, balance]; } else { throw Error("Can't unregister the account with the positive balance without force"); } } else { near.log(`The account ${account_id} is not registered`); return null; } } internal_storage_balance_of(account_id: AccountId): Option { if (this.accounts.containsKey(account_id)) { return new StorageBalance(this.storage_balance_bounds().min, 0n); } else { return null; } } /** Implementation of StorageManagement * @param registration_only doesn't affect the implementation for vanilla fungible token. */ storage_deposit( { account_id, registration_only, }: { account_id?: AccountId, registration_only?: boolean, } ): StorageBalance { let amount: Balance = near.attachedDeposit(); account_id = account_id ?? near.predecessorAccountId(); if (this.accounts.containsKey(account_id)) { near.log!("The account is already registered, refunding the deposit"); if (amount > 0) { NearPromise.new(near.predecessorAccountId()).transfer(amount); } } else { let min_balance: Balance = this.storage_balance_bounds().min; if (amount < min_balance) { throw Error("The attached deposit is less than the minimum storage balance"); } this.internal_register_account(account_id); let refund: Balance = amount - min_balance; if (refund > 0) { NearPromise.new(near.predecessorAccountId()).transfer(refund); } } return this.internal_storage_balance_of(account_id); } /** * While storage_withdraw normally allows the caller to retrieve `available` balance, the basic * Fungible Token implementation sets storage_balance_bounds.min == storage_balance_bounds.max, * which means available balance will always be 0. So this implementation: * - panics if `amount > 0` * - never transfers Ⓝ to caller * - returns a `storage_balance` struct if `amount` is 0 */ storage_withdraw({ amount }: { amount?: bigint }): StorageBalance { amount = BigInt(amount); assert_one_yocto(); let predecessor_account_id: AccountId = near.predecessorAccountId(); const storage_balance = this.internal_storage_balance_of(predecessor_account_id); if (storage_balance) { if (amount && amount > 0) { throw Error("The amount is greater than the available storage balance"); } return storage_balance; } else { throw Error(`The account ${predecessor_account_id} is not registered`) } } storage_unregister({ force }: { force?: boolean }): boolean { return this.internal_storage_unregister(force) ? true : false; } storage_balance_bounds(): StorageBalanceBounds { let required_storage_balance: Balance = this.account_storage_usage * near.storageByteCost(); return new StorageBalanceBounds(required_storage_balance, required_storage_balance); } storage_balance_of({ account_id }: { account_id: AccountId }): Option { return this.internal_storage_balance_of(account_id); } /** Implementation of FungibleTokenResolver */ ft_resolve_transfer({ sender_id, receiver_id, amount }: { sender_id: AccountId, receiver_id: AccountId, amount: Balance }): Balance { amount = BigInt(amount); const res = this.internal_ft_resolve_transfer(sender_id, receiver_id, amount); const used_amount = res[0]; const burned_amount = res[1]; if (burned_amount > 0) { near.log(`Account @${sender_id} burned ${burned_amount}`); } return used_amount; } bigIntMax = (...args: bigint[]) => args.reduce((m, e) => e > m ? e : m); bigIntMin = (...args: bigint[]) => args.reduce((m, e) => e < m ? e : m); static reconstruct(data: FungibleToken): FungibleToken { const ret = new FungibleToken(); Object.assign(ret, data); if (ret.accounts) { ret.accounts = LookupMap.reconstruct(ret.accounts); } if (ret.total_supply) { ret.total_supply = BigInt(ret.total_supply) as Balance; } if (ret.account_storage_usage) { ret.account_storage_usage = BigInt(ret.account_storage_usage) as StorageUsage; } return ret; } } ================================================ FILE: packages/near-contract-standards/src/fungible_token/events.ts ================================================ /** * Standard for nep141 (Fungible Token) events. * * These events will be picked up by the NEAR indexer. * * * * This is an extension of the events format (nep-297): * * * The three events in this standard are [`FtMint`], [`FtTransfer`], and [`FtBurn`]. * * These events can be logged by calling `.emit()` on them if a single event, or calling * [`FtMint::emit_many`], [`FtTransfer::emit_many`], * or [`FtBurn::emit_many`] respectively. */ import { NearEvent } from "../event"; import { Option } from "../non_fungible_token/utils"; import { AccountId, Balance } from "near-sdk-js"; import { toSnakeCase } from "../util"; export type Nep141EventKind = FtMint[] | FtTransfer[] | FtBurn[]; export class Nep141Event extends NearEvent { standard: string; version: string; event: string; data: Nep141EventKind; constructor(version: string, event_kind: Nep141EventKind) { super(); this.standard = "nep141" this.version = version this.event = toSnakeCase(event_kind[0].constructor.name) this.data = event_kind } } /** Data to log for an FT mint event. To log this event, call [`.emit()`](FtMint::emit). */ export class FtMint { owner_id: AccountId; amount: number; memo: Option; constructor(owner_id: AccountId, amount: number, memo: Option) { this.owner_id = owner_id; this.amount = amount; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]) } /** Emits an FT mint event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtMint`] represents the data of each mint. */ emit_many(data: FtMint[]) { new_141_v1(data).emit() } } /** Data to log for an FT transfer event. To log this event, * call [`.emit()`](FtTransfer::emit). */ export class FtTransfer { old_owner_id: AccountId; new_owner_id: AccountId; amount: string; memo: Option; constructor(old_owner_id: AccountId, new_owner_id: AccountId, amount: bigint, memo: Option) { this.old_owner_id = old_owner_id; this.new_owner_id = new_owner_id; this.amount = amount.toString(); this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]) } /** Emits an FT transfer event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtTransfer`] represents the data of each transfer. */ emit_many(data: FtTransfer[]) { new_141_v1(data).emit() } } /** Data to log for an FT burn event. To log this event, call [`.emit()`](FtBurn::emit). */ export class FtBurn { owner_id: AccountId; amount: string; memo: Option; constructor(owner_id: AccountId, amount: Balance, memo: Option) { this.owner_id = owner_id; this.amount = amount.toString(); this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { this.emit_many([this]) } /** Emits an FT burn event, through [`env::log_str`](near_sdk::env::log_str), * where each [`FtBurn`] represents the data of each burn. */ emit_many(data: FtBurn[]) { new_141_v1(data).emit() } } function new_141(version: string, event_kind: Nep141EventKind): NearEvent { return new Nep141Event(version, event_kind); } function new_141_v1(event_kind: Nep141EventKind) : NearEvent { return new_141("1.0.0", event_kind) } ================================================ FILE: packages/near-contract-standards/src/fungible_token/index.ts ================================================ export * from './core_impl'; export * from './core'; export * from './events'; export * from './metadata'; export * from './receiver'; export * from './resolver'; ================================================ FILE: packages/near-contract-standards/src/fungible_token/metadata.ts ================================================ import { assert, } from "near-sdk-js"; import { Option } from "../non_fungible_token/utils"; const FT_METADATA_SPEC: string = "ft-1.0.0"; /** * Return metadata for the token, up to contract to implement. */ export class FungibleTokenMetadata { spec: string; name: string; symbol: string; icon: Option; reference: Option; reference_hash: Option; decimals: number; constructor( spec: string, name: string, symbol: string, icon: Option, referance: Option, referance_hash: Option, decimals: number, ) { this.spec = spec; this.name = name; this.symbol = symbol; this.icon = icon; this.reference = referance; this.reference_hash = referance_hash; this.decimals = decimals; } assert_valid() { assert(this.spec == FT_METADATA_SPEC, "Invalid FT_METADATA_SPEC"); const isReferenceProvided: boolean = this.reference ? true : false; const isReferenceHashProvided: boolean = this.reference_hash ? true : false; assert(isReferenceHashProvided === isReferenceProvided, "reference and reference_hash must be either both provided or not"); if (this.reference_hash) { assert(this.reference_hash.length === 32, "reference_hash must be 32 bytes"); } } } export interface FungibleTokenMetadataProvider { ft_metadata() : FungibleTokenMetadata; } ================================================ FILE: packages/near-contract-standards/src/fungible_token/receiver.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; /** * Provides token transfer resolve functionality. * * # Examples * * ```typescript * import { AccountId, PromiseOrValue } from "near-sdk-js"; * import { FungibleTokenCore, FungibleToken, FungibleTokenReceiver } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore, FungibleTokenReceiver { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({}) * ft_on_transfer({ sender_id, amount, msg }: { * sender_id: AccountId; * amount: number; * msg: String; * }): PromiseOrValue { * return this.token.ft_on_transfer({ sender_id, amount, msg }); * }; * } * ``` */ export interface FungibleTokenReceiver { /** * Called by fungible token contract after `ft_transfer_call` was initiated by * `sender_id` of the given `amount` with the transfer message given in `msg` field. * The `amount` of tokens were already transferred to this contract account and ready to be used. * * The method must return the amount of tokens that are *not* used/accepted by this contract from the transferred * amount. Examples: * - The transferred amount was `500`, the contract completely takes it and must return `0`. * - The transferred amount was `500`, but this transfer call only needs `450` for the action passed in the `msg` * field, then the method must return `50`. * - The transferred amount was `500`, but the action in `msg` field has expired and the transfer must be * cancelled. The method must return `500` or panic. * * Arguments: * @param sender_id - the account ID that initiated the transfer. * @param amount - the amount of tokens that were transferred to this account in a decimal string representation. * @param msg - a string message that was passed with this transfer call. * * @returns the amount of unused tokens that should be returned to sender, in a decimal string representation. */ ft_on_transfer({ sender_id, amount, msg }: { sender_id: AccountId, amount: number, msg: String } ): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/src/fungible_token/resolver.ts ================================================ import { AccountId, Balance } from "near-sdk-js"; /** * Provides token transfer resolve functionality. * * # Examples * * ```typescript * import { AccountId, Balance, call } from "near-sdk-js"; * import { * FungibleTokenCore, * FungibleTokenResolver, * FungibleToken, * } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements FungibleTokenCore, FungibleTokenResolver { * private token: FungibleToken; * * constructor() { * this.token = new FungibleToken(); * } * * @call({ privateFunction: true }) * ft_resolve_transfer({ * sender_id, * receiver_id, * amount, * }: { * sender_id: AccountId, * receiver_id: AccountId, * amount: Balance * }): Balance { * const { used_amount, burned_amount } = this.token.internal_ft_resolve_transfer(sender_id, receiver_id, amount); * if (burned_amount > 0) { * console.log(`Account @${sender_id} burned ${burned_amount}`); * } * return used_amount; * } * } * ``` */ export interface FungibleTokenResolver { /** * Resolves the transfer of tokens between `sender_id` and `receiver_id`. * * @param sender_id - The account ID of the sender. * @param receiver_id - The account ID of the receiver. * @param amount - The amount of tokens to resolve in a decimal string representation. * * @returns The amount of tokens used during the transfer, returning the balance as a `Balance`. */ ft_resolve_transfer({ sender_id, receiver_id, amount, }: { sender_id: AccountId, receiver_id: AccountId, amount: Balance, }): Balance; } ================================================ FILE: packages/near-contract-standards/src/index.ts ================================================ /** Non-fungible tokens as described in [by the spec](https://nomicon.io/Standards/NonFungibleToken). */ export * from "./non_fungible_token"; /** Fungible tokens as described in [by the spec](https://nomicon.io/Standards/FungibleToken). */ export * from "./fungible_token"; export * from "./storage_management"; ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/approval/approval_receiver.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; import { TokenId } from "../token"; /** Approval receiver is the interface for the method called (or attempted to be called) when an NFT contract adds an approval for an account. */ export interface NonFungibleTokenApprovalReceiver { /** Respond to notification that contract has been granted approval for a token. * * Notes * - Contract knows the token contract ID from `predecessor_account_id` * * @param token_id - The token to which this contract has been granted approval * @param owner_id - The owner of the token * @param approval_id - The approval ID stored by NFT contract for this approval. * Expected to be a number within the 2^53 limit representable by JSON. * @param msg: - specifies information needed by the approved contract in order to handle the approval. Can indicate both a function to call and the parameters to pass to that function. */ nft_on_approve({ token_id, owner_id, approval_id, msg, }: { token_id: TokenId; owner_id: AccountId; approval_id: bigint; msg: string; }): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/approval/index.ts ================================================ import { AccountId, NearPromise } from "near-sdk-js"; import { TokenId } from "../token"; import { Option } from "../utils"; /** Interface used when it's desired to have a non-fungible token that has a * traditional escrow or approval system. This allows Alice to allow Bob * to take only the token with the unique identifier "19" but not others. * It should be noted that in the [core non-fungible token standard] there * is a method to do "transfer and call" which may be preferred over using * an approval management standard in certain use cases. * * [approval management standard]: https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html * [core non-fungible token standard]: https://nomicon.io/Standards/NonFungibleToken/Core.html */ export interface NonFungibleTokenApproval { /** Add an approved account for a specific token. * * Requirements * - Caller of the method must attach a deposit of at least 1 yoctoⓃ for * security purposes * - Contract MAY require caller to attach larger deposit, to cover cost of * storing approver data * - Contract MUST panic if called by someone other than token owner * - Contract MUST panic if addition would cause `nft_revoke_all` to exceed * single-block gas limit * - Contract MUST increment approval ID even if re-approving an account * - If successfully approved or if had already been approved, and if `msg` is * present, contract MUST call `nft_on_approve` on `account_id`. See * `nft_on_approve` description below for details. * * @param token_id - The token for which to add an approval * @param account_id - The account to add to `approvals` * @param msg - Optional string to be passed to `nft_on_approve` * @returns void, if no `msg` given. Otherwise, returns promise call to * `nft_on_approve`, which can resolve with whatever it wants. */ nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; account_id: AccountId; msg?: string; }): Option; /** Revoke an approved account for a specific token. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - If contract requires >1yN deposit on `nft_approve`, contract * MUST refund associated storage deposit when owner revokes approval * - Contract MUST panic if called by someone other than token owner * * @param token_id - The token for which to revoke an approval * @param account_id - The account to remove from `approvals` */ nft_revoke({ token_id, account_id, }: { token_id: TokenId; account_id: AccountId; }); /** Revoke all approved accounts for a specific token. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - If contract requires >1yN deposit on `nft_approve`, contract * MUST refund all associated storage deposit when owner revokes approvals * - Contract MUST panic if called by someone other than token owner * * @param token_id - The token with approvals to revoke */ nft_revoke_all({ token_id }: { token_id: TokenId }); /** Check if a token is approved for transfer by a given account, optionally * checking an approval_id * * @param token_id - The token for which to revoke an approval * @param approved_account_id - The account to check the existence of in `approvals` * @param approval_id - An optional approval ID to check against current approval ID for given account * @returns if `approval_id` given, `true` if `approved_account_id` is approved with given `approval_id` * otherwise, `true` if `approved_account_id` is in list of approved accounts */ nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; approval_id?: bigint; }): boolean; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/core/index.ts ================================================ import { AccountId } from "near-sdk-js"; import { Token, TokenId } from "../token"; import { Option } from "../utils"; /** Used for all non-fungible tokens. The specification for the * [core non-fungible token standard] lays out the reasoning for each method. * It's important to check out [NonFungibleTokenReceiver](./receiver.ts) * and [NonFungibleTokenResolver](./resolver.ts) to * understand how the cross-contract call work. * * [core non-fungible token standard]: * * # Examples * * ```typescript * import { AccountId } from "near-sdk-js"; * import { Option } from "../non_fungible_token/utils"; * import { Token, TokenId } from "../token"; * import { NonFungibleTokenCore, NonFungibleToken } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements NonFungibleTokenCore { * private token: NonFungibleToken; * * constructor() { * this.tokens = new NonFungibleToken(); * } * * @call({}) * nft_transfer({ receiver_id, token_id, approval_id, memo }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }): any { * this.tokens.nft_transfer({ receiver_id, token_id, approval_id, memo }); * } * * @call({}) * nft_transfer_call({ receiver_id, token_id, approval_id, memo }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }): any { * return this.tokens.nft_transfer_call({ receiver_id, token_id, approval_id, memo }); * } * * @view({}) * nft_token({ token_id }: { token_id: TokenId; }): Option { return this.tokens.nft_token({ token_id }); }; * } * ``` */ export interface NonFungibleTokenCore { /** Simple transfer. Transfer a given `token_id` from current owner to * `receiver_id`. * * Requirements * - Caller of the method must attach a deposit of 1 yoctoⓃ for security purposes * - Contract MUST panic if called by someone other than token owner or, * if using Approval Management, one of the approved accounts * - `approval_id` is for use with Approval Management, * see * - If using Approval Management, contract MUST nullify approved accounts on * successful transfer. * - TODO: needed? Both accounts must be registered with the contract for transfer to * succeed. See see * * @param receiver_id - The valid NEAR account receiving the token * @param token_id - The token to transfer * @param approval_id - Expected approval ID. A number smaller than * 2^53, and therefore representable as JSON. See Approval Management * standard for full explanation. * @param memo (optional) - For use cases that may benefit from indexing or * providing information for a transfer */ nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }); /** Transfer token and call a method on a receiver contract. A successful * workflow will end in a success execution outcome to the callback on the NFT * contract at the method `nft_resolve_transfer`. * * You can think of this as being similar to attaching native NEAR tokens to a * function call. It allows you to attach any Non-Fungible Token in a call to a * receiver contract. * * Requirements: * - Caller of the method must attach a deposit of 1 yoctoⓃ for security * purposes * - Contract MUST panic if called by someone other than token owner or, * if using Approval Management, one of the approved accounts * - The receiving contract must implement `ft_on_transfer` according to the * standard. If it does not, FT contract's `ft_resolve_transfer` MUST deal * with the resulting failed cross-contract call and roll back the transfer. * - Contract MUST implement the behavior described in `ft_resolve_transfer` * - `approval_id` is for use with Approval Management extension, see * that document for full explanation. * - If using Approval Management, contract MUST nullify approved accounts on * successful transfer. * * @param receiver_id - The valid NEAR account receiving the token. * @param token_id - The token to send. * @param approval_id - Expected approval ID. A number smaller than * 2^53, and therefore representable as JSON. See Approval Management * standard for full explanation. * @param memo (optional) - For use cases that may benefit from indexing or * providing information for a transfer. * @param msg - Specifies information needed by the receiving contract in * order to properly handle the transfer. Can indicate both a function to * call and the parameters to pass to that function. */ nft_transfer_call({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }); /** Returns the token with the given `token_id` or `null` if no such token. */ nft_token({ token_id }: { token_id: TokenId }): Option; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/core/receiver.ts ================================================ import { AccountId, PromiseOrValue } from "near-sdk-js"; import { TokenId } from "../token"; /** Used when an NFT is transferred using `nft_transfer_call`. This interface is implemented on the receiving contract, not on the NFT contract. */ export interface NonFungibleTokenReceiver { /** Take some action after receiving a non-fungible token * * Requirements: * - Contract MUST restrict calls to this function to a set of whitelisted NFT * contracts * * @param sender_id - The sender of `nft_transfer_call` * @param previous_owner_id - The account that owned the NFT prior to it being * transferred to this contract, which can differ from `sender_id` if using * Approval Management extension * @param token_id - The `token_id` argument given to `nft_transfer_call` * @param msg - Information necessary for this contract to know how to process the * request. This may include method names and/or arguments. * * @returns true if token should be returned to `sender_id` */ nft_on_transfer({ sender_id, previous_owner_id, token_id, msg, }: { sender_id: AccountId; previous_owner_id: AccountId; token_id: TokenId; msg: string; }): PromiseOrValue; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/core/resolver.ts ================================================ import { AccountId } from "near-sdk-js"; import { TokenId } from "../token"; /** Used when an NFT is transferred using `nft_transfer_call`. This is the method that's called after `nft_on_transfer`. This interface is implemented on the NFT contract. * * # Examples * * ```typescript * import { AccountId } from "near-sdk-js"; * import { TokenId } from "../token"; * import { NonFungibleTokenCore, NonFungibleToken, NonFungibleTokenResolver } from "near-contract-standards/lib" * * @NearBindgen({ requireInit: false }) * export class Contract implements NonFungibleTokenCore, NonFungibleTokenResolver { * private token: NonFungibleToken; * * constructor() { * this.tokens = new NonFungibleToken(); * } * * @call({}) * nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approval: AccountId]: bigint; }; }): boolean; { return this.tokens.nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }); } * } * ``` */ export interface NonFungibleTokenResolver { /** Finalize an `nft_transfer_call` chain of cross-contract calls. * * The `nft_transfer_call` process: * * 1. Sender calls `nft_transfer_call` on FT contract * 2. NFT contract transfers token from sender to receiver * 3. NFT contract calls `nft_on_transfer` on receiver contract * 4+. [receiver contract may make other cross-contract calls] * N. NFT contract resolves promise chain with `nft_resolve_transfer`, and may * transfer token back to sender * * Requirements: * - Contract MUST forbid calls to this function by any account except self * - If promise chain failed, contract MUST revert token transfer * - If promise chain resolves with `true`, contract MUST return token to * `sender_id` * * @param previous_owner_id - The owner prior to the call to `nft_transfer_call` * @param receiver_id - The `receiver_id` argument given to `nft_transfer_call` * @param token_id - The `token_id` argument given to `ft_transfer_call` * @param approved_account_ids - If using Approval Management, contract MUST provide * set of original approved accounts in this argument, and restore these * approved accounts in case of revert. * * @returns true if token was successfully transferred to `receiver_id`. */ nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approval: AccountId]: bigint }; }): boolean; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/enumeration/index.ts ================================================ import { AccountId } from "near-sdk-js"; import { Token } from "../token"; /** Offers methods helpful in determining account ownership of NFTs and provides a way to page through NFTs per owner, determine total supply, etc. */ export interface NonFungibleTokenEnumeration { /** Returns the total supply of non-fungible tokens */ nft_total_supply(): number; /** Get a list of all tokens * * @param from_index - The starting index of tokens to return * @param limit - The maximum number of tokens to return * @returns - An array of Token objects, as described in Core standard */ nft_tokens({ from_index, limit, }: { from_index?: number; // default: "0" limit?: number; // default: unlimited (could fail due to gas limit) }): Token[]; /** Get number of tokens owned by a given account * * @param account_id - A valid NEAR account * @returns - The number of non-fungible tokens owned by given `account_id` */ nft_supply_for_owner({ account_id }: { account_id: AccountId }): number; /** Get list of all tokens owned by a given account * * @param account_id - A valid NEAR account * @param from_index - The starting index of tokens to return * @param limit - The maximum number of tokens to return * @returns - A paginated list of all tokens owned by this account */ nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; from_index?: number; // default: "0" limit?: number; // default: unlimited (could fail due to gas limit) }): Token[]; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/events.ts ================================================ /** Standard for nep171 (Non-Fungible Token) events. * * These events will be picked up by the NEAR indexer. * * * * This is an extension of the events format (nep-297): * * * The three events in this standard are [`NftMint`], [`NftTransfer`], and [`NftBurn`]. * * These events can be logged by calling `.emit()` on them if a single event, or calling * [`NftMint.emit_many`], [`NftTransfer.emit_many`], * or [`NftBurn.emit_many`] respectively. */ import { AccountId } from "near-sdk-js"; import { NearEvent } from "../event"; import { TokenId } from "./token"; import { toSnakeCase } from "../util"; export type Nep171EventKind = NftMint[] | NftTransfer[] | NftBurn[] | NftContractMetadataUpdate[]; export class Nep171Event extends NearEvent { standard: string; version: string; event: string; data: Nep171EventKind; constructor(version: string, event_kind: Nep171EventKind) { super(); this.standard = "nep171" this.version = version this.event = toSnakeCase(event_kind[0].constructor.name) this.data = event_kind } } /** Data to log for an NFT mint event. To log this event, call `.emit()` */ export class NftMint { constructor( public owner_id: AccountId, public token_ids: TokenId[], public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftMint.emit_many([this]); } /** Emits an nft mint event, through `near.log`, * where each [`NftMint`] represents the data of each mint. */ static emit_many(data: NftMint[]) { new_171_v1(data).emit(); } } /** Data to log for an NFT transfer event. To log this event, * call [`.emit()`](NftTransfer.emit). */ export class NftTransfer { constructor( public old_owner_id: AccountId, public new_owner_id: AccountId, public token_ids: TokenId[], public authorized_id?: AccountId, public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftTransfer.emit_many([this]); } /** Emits an nft transfer event, through `near.log`, * where each [`NftTransfer`] represents the data of each transfer. */ static emit_many(data: NftTransfer[]) { new_171_v1(data).emit(); } } /** Data to log for an NFT burn event. To log this event, call [`.emit()`](NftBurn.emit). */ export class NftBurn { constructor( public owner_id: AccountId, public token_ids: TokenId[], public authorized_id?: string, public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftBurn.emit_many([this]); } /** Emits an nft burn event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data: NftBurn[]) { new_171_v1(data).emit(); } } /** Data to log for an NFT contract metadata updates. To log this event, call [`.emit()`](NftContractMetadataUpdate.emit). */ export class NftContractMetadataUpdate { constructor( public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit() { NftContractMetadataUpdate.emit_many([this]); } /** Emits an contract metadata update event, through `near.log`, * where each [`NftBurn`] represents the data of each burn. */ static emit_many(data: NftContractMetadataUpdate[]) { new_171_v1(data).emit(); } } function new_171(version: string, event_kind: Nep171EventKind): NearEvent { return new Nep171Event(version, event_kind); } function new_171_v1(event_kind: Nep171EventKind): NearEvent { return new_171("1.0.0", event_kind); } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/impl.ts ================================================ import { AccountId, UnorderedMap, LookupMap, near, UnorderedSet, assert, NearPromise, bytes, serialize, str, IntoStorageKey, } from "near-sdk-js"; import { TokenMetadata } from "./metadata"; import { refund_storage_deposit, refund_deposit, refund_deposit_to_account, assert_at_least_one_yocto, Option, assert_one_yocto, } from "./utils"; import { NftMint, NftTransfer } from "./events"; import { NonFungibleTokenResolver } from "./core/resolver"; import { Token, TokenId } from "./token"; import { NonFungibleTokenCore } from "./core"; import { NonFungibleTokenApproval } from "./approval"; import { NonFungibleTokenEnumeration } from "./enumeration"; const GAS_FOR_RESOLVE_TRANSFER = 16_000_000_000_000n; const GAS_FOR_NFT_TRANSFER_CALL = 30_000_000_000_000n + GAS_FOR_RESOLVE_TRANSFER; const GAS_FOR_NFT_APPROVE = 21_000_000_000_000n; function repeat(str: string, n: number) { return Array(n + 1).join(str); } function expect_token_found(option: Option): T { if (option === null) { throw new Error("Token not found"); } return option; } function expect_approval(option: Option): T { if (option === null) { throw new Error("next_approval_by_id must be set for approval ext"); } return option; } /** Implementation of the non-fungible token standard. * Allows to include NEP-171 compatible token to any contract. * There are next interfaces that any contract may implement: * - NonFungibleTokenCore -- interface with nft_transfer methods. NonFungibleToken provides methods for it. * - NonFungibleTokenApproval -- interface with nft_approve methods. NonFungibleToken provides methods for it. * - NonFungibleTokenEnumeration -- interface for getting lists of tokens. NonFungibleToken provides methods for it. * - NonFungibleTokenMetadata -- return metadata for the token in NEP-177, up to contract to implement. * * For example usage, see near-contract-standards/example-contracts/non-fungible-token/my-nft.ts. */ export class NonFungibleToken implements NonFungibleTokenCore, NonFungibleTokenResolver, NonFungibleTokenApproval, NonFungibleTokenEnumeration { public owner_id: AccountId; public extra_storage_in_bytes_per_token: bigint; public owner_by_id: UnorderedMap; public token_metadata_by_id: Option>; public tokens_per_owner: Option>>; public approvals_by_id: Option>; public next_approval_id_by_id: Option>; constructor() { this.owner_id = ""; this.extra_storage_in_bytes_per_token = 0n; this.owner_by_id = new UnorderedMap(""); this.token_metadata_by_id = null; this.tokens_per_owner = null; this.approvals_by_id = null; this.next_approval_id_by_id = null; } nft_total_supply(): number { return this.owner_by_id.length; } private enum_get_token(owner_id: AccountId, token_id: TokenId): Token { const metadata = this.token_metadata_by_id.get(token_id, { reconstructor: TokenMetadata.reconstruct, }); const approved_account_ids = this.approvals_by_id.get(token_id, { defaultValue: {}, }); return new Token(token_id, owner_id, metadata, approved_account_ids); } nft_tokens({ from_index, limit, }: { from_index?: number; limit?: number; }): Token[] { const start_index = from_index === undefined ? 0 : from_index; assert( this.owner_by_id.length >= start_index, "Out of bounds, please use a smaller from_index." ); let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, this.owner_by_id.length - start_index); const ret: Token[] = []; const tokens = this.owner_by_id.keys({start: from_index, limit: l}); for (let token_id of tokens) { const owner_id = this.owner_by_id.get(token_id); ret.push(this.enum_get_token(owner_id, token_id)); } return ret; } nft_supply_for_owner({ account_id }: { account_id: AccountId }): number { const tokens_per_owner = this.tokens_per_owner; assert( tokens_per_owner !== null, "Could not find tokens_per_owner when calling a method on the enumeration standard." ); const account_tokens = tokens_per_owner.get(account_id, { reconstructor: UnorderedSet.reconstruct, }); return account_tokens === null ? 0 : account_tokens.length; } nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; from_index?: number; limit?: number; }): Token[] { const tokens_per_owner = this.tokens_per_owner; assert( tokens_per_owner !== undefined, "Could not find tokens_per_owner when calling a method on the enumeration standard." ); const token_set = tokens_per_owner.get(account_id, { reconstructor: UnorderedSet.reconstruct, }); assert(token_set !== null, "Token set is empty"); const start_index = from_index === undefined ? 0 : from_index; assert( token_set.length >= start_index, "Out of bounds, please use a smaller from_index." ); let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, token_set.length - start_index); const ret: Token[] = []; const tokens = token_set.elements({start: from_index, limit: l}); for (let token_id of tokens) { const owner_id = this.owner_by_id.get(token_id); ret.push(this.enum_get_token(owner_id, token_id)); } return ret; } nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; account_id: AccountId; msg: string; }): Option { assert_at_least_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); assert( near.predecessorAccountId() === owner_id, "Predecessor must be token owner." ); const next_approval_id_by_id = expect_approval(this.next_approval_id_by_id); const approved_account_ids = approvals_by_id.get(token_id) ?? {}; const approval_id = next_approval_id_by_id.get(token_id) ?? 1n; const old_approved_account_ids_size = serialize(approved_account_ids).length; approved_account_ids[account_id] = approval_id; const new_approved_account_ids_size = serialize(approved_account_ids).length; approvals_by_id.set(token_id, approved_account_ids); next_approval_id_by_id.set(token_id, approval_id + 1n); const storage_used = new_approved_account_ids_size - old_approved_account_ids_size; refund_deposit(BigInt(storage_used)); if (msg) { return NearPromise.new(account_id).functionCallRaw( "nft_on_approve", serialize({ token_id, owner_id, approval_id, msg }), 0n, near.prepaidGas() - GAS_FOR_NFT_APPROVE ); } return null; } nft_revoke({ token_id, account_id, }: { token_id: TokenId; account_id: AccountId; }) { assert_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); const predecessorAccountId = near.predecessorAccountId(); assert( predecessorAccountId === owner_id, "Predecessor must be token owner." ); const approved_account_ids = approvals_by_id.get(token_id); const old_approved_account_ids_size = serialize(approved_account_ids).length; let new_approved_account_ids_size; if (approved_account_ids[account_id]) { delete approved_account_ids[account_id]; if (Object.keys(approved_account_ids).length === 0) { approvals_by_id.remove(token_id); new_approved_account_ids_size = serialize(approved_account_ids).length; } else { approvals_by_id.set(token_id, approved_account_ids); new_approved_account_ids_size = 0; } refund_storage_deposit( predecessorAccountId, new_approved_account_ids_size - old_approved_account_ids_size ); } } nft_revoke_all({ token_id }: { token_id: TokenId }) { assert_one_yocto(); if (this.approvals_by_id === null) { throw new Error("NFT does not support Approval Management"); } const approvals_by_id = this.approvals_by_id; const owner_id = expect_token_found(this.owner_by_id.get(token_id)); const predecessorAccountId = near.predecessorAccountId(); assert( predecessorAccountId === owner_id, "Predecessor must be token owner." ); const approved_account_ids = approvals_by_id.get(token_id); if (approved_account_ids) { refund_storage_deposit( predecessorAccountId, serialize(approved_account_ids).length ); approvals_by_id.remove(token_id); } } nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; approval_id?: bigint; }): boolean { expect_token_found(this.owner_by_id.get(token_id)); if (this.approvals_by_id === null) { return false; } const approvals_by_id = this.approvals_by_id; const approved_account_ids = approvals_by_id.get(token_id); if (approved_account_ids === null) { return false; } const actual_approval_id = approved_account_ids[approved_account_id]; if (actual_approval_id === undefined) { return false; } if (approval_id) { return BigInt(approval_id) === actual_approval_id; } return true; } init( owner_by_id_prefix: IntoStorageKey, owner_id: AccountId, token_metadata_prefix?: IntoStorageKey, enumeration_prefix?: IntoStorageKey, approval_prefix?: IntoStorageKey ) { let approvals_by_id: Option>; let next_approval_id_by_id: Option>; if (approval_prefix) { const prefix = approval_prefix.into_storage_key(); approvals_by_id = new LookupMap(prefix); next_approval_id_by_id = new LookupMap(prefix + "n"); } else { approvals_by_id = null; next_approval_id_by_id = null; } this.owner_id = owner_id; this.extra_storage_in_bytes_per_token = 0n; this.owner_by_id = new UnorderedMap( owner_by_id_prefix.into_storage_key() ); this.token_metadata_by_id = token_metadata_prefix ? new LookupMap(token_metadata_prefix.into_storage_key()) : null; this.tokens_per_owner = enumeration_prefix ? new LookupMap(enumeration_prefix.into_storage_key()) : null; this.approvals_by_id = approvals_by_id; this.next_approval_id_by_id = next_approval_id_by_id; this.measure_min_token_storage_cost(); } static reconstruct(data: NonFungibleToken): NonFungibleToken { const ret = new NonFungibleToken(); Object.assign(ret, data); ret.owner_by_id = UnorderedMap.reconstruct(ret.owner_by_id); if (ret.token_metadata_by_id) { ret.token_metadata_by_id = LookupMap.reconstruct( ret.token_metadata_by_id ); } if (ret.tokens_per_owner) { ret.tokens_per_owner = LookupMap.reconstruct(ret.tokens_per_owner); } if (ret.approvals_by_id) { ret.approvals_by_id = LookupMap.reconstruct(ret.approvals_by_id); } if (ret.next_approval_id_by_id) { ret.next_approval_id_by_id = LookupMap.reconstruct( ret.next_approval_id_by_id ); } return ret; } measure_min_token_storage_cost() { const initial_storage_usage = near.storageUsage(); // 64 Length because this is the max account id length const tmp_token_id = repeat("a", 64); const tmp_owner_id = repeat("a", 64); // 1. set some dummy data this.owner_by_id.set(tmp_token_id, tmp_owner_id); if (this.token_metadata_by_id) { this.token_metadata_by_id.set( tmp_token_id, new TokenMetadata( repeat("a", 64), repeat("a", 64), repeat("a", 64), repeat("a", 64), 1n, null, null, null, null, null, null, null ) ); } if (this.tokens_per_owner) { const u = new UnorderedSet( new TokensPerOwner( near.sha256(bytes(tmp_owner_id)) ).into_storage_key() ); u.set(tmp_token_id); this.tokens_per_owner.set(tmp_owner_id, u); } if (this.approvals_by_id) { const approvals = {}; approvals[tmp_owner_id] = 1n; this.approvals_by_id.set(tmp_token_id, approvals); } if (this.next_approval_id_by_id) { this.next_approval_id_by_id.set(tmp_token_id, 1n); } // 2. see how much space it took this.extra_storage_in_bytes_per_token = near.storageUsage() - initial_storage_usage; // 3. roll it all back if (this.next_approval_id_by_id) { this.next_approval_id_by_id.remove(tmp_token_id); } if (this.approvals_by_id) { this.approvals_by_id.remove(tmp_token_id); } if (this.tokens_per_owner) { const u = this.tokens_per_owner.remove(tmp_owner_id, { reconstructor: UnorderedSet.reconstruct, }); u.remove(tmp_token_id); } if (this.token_metadata_by_id) { this.token_metadata_by_id.remove(tmp_token_id); } this.owner_by_id.remove(tmp_token_id); } internal_transfer_unguarded( token_id: TokenId, from: AccountId, to: AccountId ) { this.owner_by_id.set(token_id, to); if (this.tokens_per_owner) { const owner_tokens_set = this.tokens_per_owner.get(from, { reconstructor: UnorderedSet.reconstruct, }); if (owner_tokens_set == null) { throw new Error("Unable to access tokens per owner in unguarded call."); } owner_tokens_set.remove(token_id); if (owner_tokens_set.isEmpty()) { this.tokens_per_owner.remove(from); } else { this.tokens_per_owner.set(from, owner_tokens_set); } let receiver_tokens_set = this.tokens_per_owner.get(to, { reconstructor: UnorderedSet.reconstruct, }); if (receiver_tokens_set === null) { receiver_tokens_set = new UnorderedSet( new TokensPerOwner(near.sha256(bytes(to))).into_storage_key() ); } receiver_tokens_set.set(token_id); this.tokens_per_owner.set(to, receiver_tokens_set); } } internal_transfer( sender_id: AccountId, receiver_id: AccountId, token_id: TokenId, approval_id?: bigint, memo?: string ): [AccountId, Option<{ [approvals: AccountId]: bigint }>] { const owner_id = this.owner_by_id.get(token_id); if (owner_id == null) { throw new Error("Token not found"); } const approved_account_ids = this.approvals_by_id?.remove(token_id); let sender_id_authorized: string | undefined; if (sender_id != owner_id) { if (!approved_account_ids) { throw new Error("Unauthorized"); } const actual_approval_id = approved_account_ids[sender_id]; if (!actual_approval_id) { throw new Error("Sender not approved"); } assert( approval_id === undefined || approval_id == actual_approval_id, `The actual approval_id ${actual_approval_id} is different from the given ${approval_id}` ); sender_id_authorized = sender_id; } else { sender_id_authorized = undefined; } assert(owner_id != receiver_id, "Current and next owner must differ"); this.internal_transfer_unguarded(token_id, owner_id, receiver_id); NonFungibleToken.emit_transfer( owner_id, receiver_id, token_id, sender_id_authorized, memo ); return [owner_id, approved_account_ids]; } static emit_transfer( owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, sender_id?: AccountId, memo?: string ) { new NftTransfer( owner_id, receiver_id, [token_id], sender_id && sender_id == owner_id ? sender_id : undefined, memo ).emit(); } internal_mint( token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata ): Token { const token = this.internal_mint_with_refund( token_id, token_owner_id, token_metadata, near.predecessorAccountId() ); new NftMint(token.owner_id, [token.token_id]).emit(); return token; } internal_mint_with_refund( token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata, refund_id?: string ): Token { let initial_storage_usage: Option<[string, bigint]> = null; if (refund_id) { initial_storage_usage = [refund_id, near.storageUsage()]; } if (this.token_metadata_by_id && token_metadata === undefined) { throw new Error("Must provide metadata"); } if (this.owner_by_id.get(token_id)) { throw new Error("token_id must be unique"); } const owner_id = token_owner_id; this.owner_by_id.set(token_id, owner_id); this.token_metadata_by_id?.set(token_id, token_metadata); if (this.tokens_per_owner) { let token_ids = this.tokens_per_owner.get(owner_id, { reconstructor: UnorderedSet.reconstruct, }); if (token_ids === null) { token_ids = new UnorderedSet( new TokensPerOwner(near.sha256(bytes(owner_id))).into_storage_key() ); } token_ids.set(token_id); this.tokens_per_owner.set(owner_id, token_ids); } const approved_account_ids = this.approvals_by_id ? {} : undefined; if (initial_storage_usage) { const [id, storage_usage] = initial_storage_usage; refund_deposit_to_account(near.storageUsage() - storage_usage, id); } return new Token(token_id, owner_id, token_metadata, approved_account_ids); } nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; }) { assert_at_least_one_yocto(); const sender_id = near.predecessorAccountId(); this.internal_transfer(sender_id, receiver_id, token_id, approval_id, memo); } nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }: { receiver_id: AccountId; token_id: TokenId; approval_id?: bigint; memo?: string; msg: string; }) { assert_at_least_one_yocto(); assert( near.prepaidGas() > GAS_FOR_NFT_TRANSFER_CALL, "Not enough prepaid gas" ); const sender_id = near.predecessorAccountId(); const [previous_owner_id, approved_account_ids] = this.internal_transfer( sender_id, receiver_id, token_id, approval_id, memo ); const promise = NearPromise.new(receiver_id) .functionCall( "nft_on_transfer", JSON.stringify({ sender_id, previous_owner_id, token_id, msg }), 0n, near.prepaidGas() - GAS_FOR_NFT_TRANSFER_CALL ) .then( NearPromise.new(near.currentAccountId()).functionCall( "nft_resolve_transfer", JSON.stringify({ previous_owner_id, receiver_id, token_id, approved_account_ids, }), 0n, GAS_FOR_RESOLVE_TRANSFER ) ); return promise; } nft_token({ token_id }: { token_id: TokenId }): Option { const owner_id = this.owner_by_id.get(token_id); if (owner_id == null) { return null; } const metadata = this.token_metadata_by_id?.get(token_id, { reconstructor: TokenMetadata.reconstruct, }); const approved_account_ids = this.approvals_by_id?.get(token_id) as Option<{ [approvals: AccountId]: bigint; }>; return new Token(token_id, owner_id, metadata, approved_account_ids); } nft_resolve_transfer({ previous_owner_id, receiver_id, token_id, approved_account_ids, }: { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; approved_account_ids?: { [approvals: AccountId]: bigint }; }): boolean { let must_revert = false; let p: string; try { p = near.promiseResult(0); } catch (e) { if (e.message.includes("Not Ready")) { throw new Error(); } else { must_revert = true; } } if (!must_revert) { try { const yes_or_no = JSON.parse(p); if (typeof yes_or_no == "boolean") { must_revert = yes_or_no; } else { must_revert = true; } } catch (_e) { must_revert = true; } } if (!must_revert) { return true; } const current_owner = this.owner_by_id.get(token_id) as Option; if (current_owner) { if (current_owner != receiver_id) { return true; } } else { if (approved_account_ids) { refund_storage_deposit( previous_owner_id, serialize(approved_account_ids).length ); } return true; } this.internal_transfer_unguarded(token_id, receiver_id, previous_owner_id); if (this.approvals_by_id) { const receiver_approvals = this.approvals_by_id.get(token_id); if (receiver_approvals) { refund_storage_deposit( receiver_id, serialize(receiver_approvals).length ); } if (approved_account_ids) { this.approvals_by_id.set(token_id, approved_account_ids); } } NonFungibleToken.emit_transfer( receiver_id, previous_owner_id, token_id, null, null ); return false; } } export type StorageKey = TokensPerOwner | TokenPerOwnerInner; export class TokensPerOwner implements IntoStorageKey { constructor(public account_hash: Uint8Array) {} into_storage_key(): string { return "\x00" + str(this.account_hash); } } export class TokenPerOwnerInner implements IntoStorageKey { constructor(public account_id_hash: Uint8Array) {} into_storage_key(): string { return "\x01" + str(this.account_id_hash); } } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/index.ts ================================================ /** The [approval management standard](https://nomicon.io/Standards/NonFungibleToken/ApprovalManagement.html) for NFTs. */ export * from "./approval"; /** The [core non-fungible token standard](https://nomicon.io/Standards/NonFungibleToken/Core.html). This can be though of as the base standard, with the others being extension standards. */ export * from "./core"; /** Interface for the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Enumeration.html). * This provides useful view-only methods returning token supply, tokens by owner, etc. */ export * from "./enumeration"; export * from "./events"; export * from "./impl"; /** Metadata interfaces and implementation according to the [NFT enumeration standard](https://nomicon.io/Standards/NonFungibleToken/Metadata.html). * This covers both the contract metadata and the individual token metadata. */ export * from "./metadata"; /** The Token struct for the non-fungible token. */ export * from "./token"; /** NFT utility functions */ export * from "./utils"; ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/metadata.ts ================================================ import { assert } from "near-sdk-js"; import { Option } from "./utils"; /** This spec can be treated like a version of the standard. */ export const NFT_METADATA_SPEC = "nft-1.0.0"; /** Metadata for the NFT contract itself. */ export class NFTContractMetadata { public spec: string; // required, essentially a version like "nft-1.0.0" public name: string; // required, ex. "Mosaics" public symbol: string; // required, ex. "MOSIAC" public icon: Option; // Data URL public base_uri: Option; // Centralized gateway known to have reliable access to decentralized storage assets referenced by `reference` or `media` URLs public reference: Option; // URL to a JSON file with more info public reference_hash: Option; // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. constructor() { this.spec = NFT_METADATA_SPEC; this.name = ""; this.symbol = ""; this.icon = null; this.base_uri = null; this.reference = null; this.reference_hash = null; } init( spec: string, name: string, symbol: string, icon: Option, base_uri: Option, reference: Option, reference_hash: Option ) { this.spec = spec; this.name = name; this.symbol = symbol; this.icon = icon; this.base_uri = base_uri; this.reference = reference; this.reference_hash = reference_hash; } assert_valid() { assert(this.spec == NFT_METADATA_SPEC, "Spec is not NFT metadata"); assert( (this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present" ); if (this.reference_hash != null) { assert(this.reference_hash.length == 32, "Hash has to be 32 bytes"); } } static reconstruct(data: NFTContractMetadata): NFTContractMetadata { const metadata = new NFTContractMetadata(); Object.assign(metadata, data); return metadata; } } /** Metadata on the individual token level. */ export class TokenMetadata { constructor( public title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" public description: Option, // free-form description public media: Option, // URL to associated media, preferably to decentralized, content-addressed storage public media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. public copies: Option, // number of copies of this set of metadata in existence when token was minted. public issued_at: Option, // ISO 8601 datetime when token was issued or minted public expires_at: Option, // ISO 8601 datetime when token expires public starts_at: Option, // ISO 8601 datetime when token starts being valid public updated_at: Option, // ISO 8601 datetime when token was last updated public extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. public reference: Option, // URL to an off-chain JSON file with more info. public reference_hash: Option // Base64-encoded sha256 hash of JSON from reference field. Required if `reference` is included. ) {} assert_valid() { assert( (this.media != null) == (this.media_hash != null), "Media and media hash must be present" ); if (this.media_hash != null) { assert(this.media_hash.length == 32, "Media hash has to be 32 bytes"); } assert( (this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present" ); if (this.reference_hash != null) { assert( this.reference_hash.length == 32, "Reference hash has to be 32 bytes" ); } } static reconstruct(data: TokenMetadata): TokenMetadata { return new TokenMetadata( data.title, data.description, data.media, data.media_hash, data.copies, data.issued_at, data.expires_at, data.starts_at, data.updated_at, data.extra, data.reference, data.reference_hash ); } } /** Offers details on the contract-level metadata. */ export interface NonFungibleTokenMetadataProvider { nft_metadata(): NFTContractMetadata; } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/token.ts ================================================ import { TokenMetadata } from "./metadata"; import { AccountId } from "near-sdk-js"; /** Note that token IDs for NFTs are strings on NEAR. It's still fine to use auto incrementing numbers as unique IDs if desired, but they should be stringified. This is to make IDs more future-proof as chain-agnostic conventions and standards arise, and allows for more flexibility with considerations like bridging NFTs across chains, etc. */ export type TokenId = string; /** In this implementation, the Token struct takes two extensions standards (metadata and approval) as optional fields, as they are frequently used in modern NFTs. */ export class Token { constructor( public token_id: TokenId, public owner_id: AccountId, public metadata?: TokenMetadata, public approved_account_ids?: { [approved_account_id: AccountId]: bigint; } ) {} } ================================================ FILE: packages/near-contract-standards/src/non_fungible_token/utils.ts ================================================ import { near, assert, AccountId, bytes } from "near-sdk-js"; export function refund_storage_deposit( account_id: AccountId, storage_released: number ): void { const promise_id = near.promiseBatchCreate(account_id); near.promiseBatchActionTransfer( promise_id, BigInt(storage_released) * near.storageByteCost() ); near.promiseReturn(promise_id); } export function refund_deposit_to_account( storage_used: bigint, account_id: AccountId ): void { const required_cost = near.storageByteCost() * storage_used; const attached_deposit = near.attachedDeposit(); assert( required_cost <= attached_deposit, `Must attach ${required_cost} yoctoNEAR to cover storage` ); const refund = attached_deposit - required_cost; if (refund > 1n) { const promise_id = near.promiseBatchCreate(account_id); near.promiseBatchActionTransfer(promise_id, refund); near.promiseReturn(promise_id); } } /** Assumes that the predecessor will be refunded */ export function refund_deposit(storage_used: bigint): void { refund_deposit_to_account(storage_used, near.predecessorAccountId()); } export function hash_account_id(account_id: AccountId): Uint8Array { return near.sha256(bytes(account_id)); } /** Assert that at least 1 yoctoNEAR was attached. */ export function assert_at_least_one_yocto(): void { assert( near.attachedDeposit() >= 1n, "Requires attached deposit of at least 1 yoctoNEAR" ); } /** Assert that exactly 1 yoctoNEAR was attached */ export function assert_one_yocto(): void { assert( near.attachedDeposit() === 1n, "Requires attached deposit of 1 yoctoNEAR" ); } export type Option = T | null; ================================================ FILE: packages/near-contract-standards/src/storage_management/index.ts ================================================ import { AccountId, Balance } from "near-sdk-js" import { Option } from "../non_fungible_token/utils"; export class StorageBalance { total: Balance; available: Balance; constructor(total: Balance, available: Balance) { this.total = total; this.available = available; } } export class StorageBalanceBounds { constructor(min: Balance, max: Option) { this.min = min; this.max = max; } min: Balance; max: Option; } export interface StorageManagement { /** * @param registration_only if `true` MUST refund above the minimum balance if the account didn't exist and * refund full deposit if the account exists. */ storage_deposit( { account_id, registration_only }: { account_id: Option, registration_only: Option, } ): StorageBalance; /** Withdraw specified amount of available Ⓝ for predecessor account. * * This method is safe to call. It MUST NOT remove data. * * @param amount is sent as a string representing an unsigned 128-bit integer. If * omitted, contract MUST refund full `available` balance. If `amount` exceeds * predecessor account's available balance, contract MUST panic. * * If predecessor account not registered, contract MUST panic. * * MUST require exactly 1 yoctoNEAR attached balance to prevent restricted * function-call access-key call (UX wallet security) * * @returns the StorageBalance structure showing updated balances. */ storage_withdraw({ amount }: { amount?: bigint }): StorageBalance; /** Unregisters the predecessor account and returns the storage NEAR deposit back. * * If the predecessor account is not registered, the function MUST return `false` without panic. * * @param force If `force=true` the function SHOULD ignore account balances (burn them) and close the account. * Otherwise, MUST panic if caller has a positive registered balance (eg token holdings) or * the contract doesn't support force unregistration. * MUST require exactly 1 yoctoNEAR attached balance to prevent restricted function-call access-key call * (UX wallet security) * @returns `true` if the account was unregistered, `false` if account was not registered before. */ storage_unregister({ force }: { force: Option }): boolean; storage_balance_bounds(): StorageBalanceBounds; storage_balance_of({ account_id }: { account_id: AccountId }): Option; } ================================================ FILE: packages/near-contract-standards/src/util.ts ================================================ export const toSnakeCase = (str: string) => { return str.replace(/[A-Z]/g, (letter, index) => { return index == 0 ? letter.toLowerCase() : '_'+ letter.toLowerCase();}); } ================================================ FILE: packages/near-contract-standards/tsconfig.json ================================================ { "compilerOptions": { "esModuleInterop": true, "lib": ["es2015", "esnext", "dom"], "module": "esnext", "target": "es2020", "moduleResolution": "node", "alwaysStrict": true, "outDir": "./lib", "declaration": true, "preserveSymlinks": true, "preserveWatchOutput": true, "pretty": false, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": false, "noImplicitReturns": true, "noUnusedLocals": true, "experimentalDecorators": true, "resolveJsonModule": true, "allowJs": true, "skipLibCheck": true }, "files": [ "src/index.ts", ], "exclude": ["node_modules"] } ================================================ FILE: packages/near-sdk-js/.eslintrc.cjs ================================================ /** @type {import('eslint').Linter.Config} */ module.exports = { root: true, env: { es2021: true, node: true, }, overrides: [ { files: ["./**/*.ts", "./**/*.js"], extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", ], parser: "@typescript-eslint/parser", parserOptions: { ecmaVersion: "latest", sourceType: "module", project: ["./{t,j}sconfig.json", "./**/{t,j}sconfig.json"], }, plugins: ["@typescript-eslint"], rules: { "@typescript-eslint/no-unused-vars": [ "warn", { varsIgnorePattern: "^_", argsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_", }, ], }, }, ], ignorePatterns: [ "pnpm-lock.yaml", "./**/node_modules", "node_modules", "./**/lib", "lib", "./**/build", "build", "./**/deps", "deps", ], rules: {}, }; ================================================ FILE: packages/near-sdk-js/.prettierignore ================================================ deps build node_modules lib cli pnpm-lock.yaml ================================================ FILE: packages/near-sdk-js/README.md ================================================ # NEAR JavaScript SDK ## Quick Start Use [`create-near-app`](https://github.com/near/create-near-app) to quickly get started writing smart contracts in JavaScript on NEAR. npx create-near-app This will scaffold a basic template for you 😎 Learn more in our [Quick Start guide](https://docs.near.org/develop/quickstart-guide). ## Running Examples There are a couple of contract examples in the project: - [Clean contract state](https://github.com/near/near-sdk-js/tree/develop/examples/src/clean-state.js) - [Counter using low level API](https://github.com/near/near-sdk-js/tree/develop/examples/src/counter/counter-lowlevel.js) - [Counter in JavaScript](https://github.com/near/near-sdk-js/tree/develop/examples/src/counter/counter.js) - [Counter in TypeScript](https://github.com/near/near-sdk-js/tree/develop/examples/src/counter/counter.ts) - [Doing cross contract call](https://github.com/near/near-sdk-js/tree/develop/examples/src//cross-contract-calls/cross-contract-call.js) - [Fungible token](https://github.com/near/near-sdk-js/tree/develop/examples/src/fungible-token/fungible-token.js) - [Lockable fungible token](https://github.com/near/near-sdk-js/tree/develop/examples/src/fungible-token/fungible-token-lockable.js) - [Non fungible token](https://github.com/near/near-sdk-js/tree/develop/examples/src/non-fungible-token/non-fungible-token.js) - [Non fungible token receiver contract](https://github.com/near/near-sdk-js/tree/develop/examples/src/non-fungible-token/non-fungible-token-receiver.js) - [Status message board](https://github.com/near/near-sdk-js/tree/develop/examples/src/status-message/status-message.js) - [Status message board with unique messages](https://github.com/near/near-sdk-js/tree/develop/examples/src/status-message/status-message-collections.js) - [Programmatic Update After Locking The Contract](https://github.com/near/near-sdk-js/tree/develop/examples/src/programmatic-updates/programmatic-update.js) To build all examples, run `pnpm build` in `examples/`. To test all examples, run `pnpm test`. You can also build and test one specific example with `pnpm build:` and `pnpm test:`, see `examples/package.json`. To deploy and call a contract on a NEAR node, use near-cli's `near deploy` and `near call`. ## Test We recommend to use near-workspaces to write tests for your smart contracts. See any of the examples for how tests are setup and written. ## Error Handling in NEAR-SDK-JS If you want to indicate an error happened and fail the transaction, just throw an error object in JavaScript. The compiled JavaScript contract includes error handling capability. It will catch throwed errors and automatically invoke `panic_utf8` with `"{error.message}\n:{error.stack}"`. As a result, transaction will fail with `"Smart contract panicked: {error.message}\n{error.stack}"` error message. You can also use an error utilities library to organize your errors, such as verror. When your JS code or library throws an error, uncaught, the transaction will also fail with GuestPanic error, with the error message and stacktrace. When call host function with inappropriate type, means incorrect number of arguments or arg is not expected type: - if arguments less than params, remaining argument are set as 'undefined' - if arguments more than params, remaining argument are ignored - if argument is different than the required type, it'll be coerced to required type - if argument is different than the required type but cannot be coerced, will throw runtime type error, also with message and stacktrace ## Migrating from near-sdk-js 0.6.0 If you have a near-sdk-js 0.6.0 contract, you need to drop the `babel.config.json` because it is now inlined in near-sdk-js CLI. Also `Bytes` type in 0.6.0 is replaced with `string` and `Uint8Array`. Because `Bytes` was an alias to `string`, this doesn't affect all collection APIs and most low level APIs. Some low level APIs below now also comes with a raw version, which ends with `Raw` and takes `Uint8Array` instead of `string`, for example, `storageRead` vs `storageReadRaw`. Some low level APIs have more sense to use `Uint8Array` instead of `string`, such as `sha256` and arguments for a function call type of promise, these are **BREAKING** changes. Please refer to next section for details: look for functions with `Uint8Array` argument and return types. ## NEAR-SDK-JS API Reference All NEAR blockchain provided functionality (host functions) are defined in `src/api.ts` and exported as `near`. You can use them by: ```js import { near } from "near-sdk-js"; // near.. e.g.: let signer = near.signerAccountId(); ``` ### About Type NEAR-SDK-JS is written in TypeScript, so every API function has a type specified by signature that looks familiar to JavaScript/TypeScript Developers. Two types in the signature need a special attention: - Most of the API take `bigint` instead of Number as type. This because JavaScript Number cannot hold 64 bit and 128 bit integer without losing precision. - For those API that takes or returns raw bytes, it is a JavaScript `Uint8Array`. You can use standard `Uint8Array` methods on it or decode it to string with `decode` or `str`. The difference between `decode` and `str` is: `decode` decode the array as UTF-8 sequence. `str` simply converts each Uint8 to one char with that char code. ### Context API - `currentAccountId()` -- Returns the ID of the current contract - the contract that is being executed. - `signerAccountId()` -- Returns the ID of the account that signed the transaction. - `signerAccountPk()` -- Returns the public key of the account that signed the transaction. - `predecessorAccountId()` -- Returns the ID of the account that called the function. - `inputRaw()` -- Returns the arguments passed to the current smart contract call. - `input()` -- Returns the arguments passed to the current smart contract call as utf-8 string. - `blockIndex()` -- Returns the current block index. **Deprecated** - `blockHeight()` -- Returns the current block height. - `blockTimestamp()` -- Returns the current block timestamp. - `epochHeight()` -- Returns the current epoch height. - `storageUsage()` -- Returns the current accounts NEAR storage usage. ### Economics API - `accountBalance()` -- Returns the current account's account balance. - `accountLockedBalance()` -- Returns the current account's locked balance. - `attachedDeposit()` -- Returns the amount of NEAR attached to this function call. Can only be called in payable functions. - `prepaidGas()` -- Returns the amount of Gas that was attached to this function call. - `usedGas()` -- Returns the amount of Gas that has been used by this function call until now. ### Math API - `altBn128G1Multiexp` -- Compute alt_bn128 g1 multiexp. `alt_bn128` is a specific curve from the Barreto-Naehrig(BN) family. It is particularly well-suited for ZK proofs. - `altBn128G1Sum` - Computes sum for signed g1 group elements on alt_bn128 curve. - `altBn128PairingCheck(value: Uint8Array)` -- Computes pairing check on alt_bn128 curve. - `randomSeed()` -- Returns a random string of bytes. - `sha256(value: Uint8Array)` - Returns sha256 hash of given value. - `keccak256(value: Uint8Array)` -- Returns keccak256 hash of given value. - `keccak512(value: Uint8Array)` -- Returns keccak512 hash of given value. - `ripemd160(value: Uint8Array)` -- Returns ripemd160 hash of given value. - `ecrecover(hash: Uint8Array, sign: Uint8Array, v: bigint, malleability_flag: bigint)` -- Recovers an ECDSA signer address from a 32-byte message hash and a corresponding signature along with v recovery byte. Takes in an additional flag to check for malleability of the signature which is generally only ideal for transactions. ### Miscellaneous API - `valueReturnRaw(value: Uint8Array)` -- Returns the value from the NEAR WASM virtual machine. - `valueReturn(value: string)` -- Returns the utf-8 string value from the NEAR WASM virtual machine. - `panicUtf8(msg: Uint8Array)` -- Panic the transaction execution with given message. - `logUtf8(msg: Uint8Array)` -- Log the message in transaction logs. - `logUtf16(msg: Uint8Array)` -- Log the message in transaction logs. - `log(...params: unknown[])` -- Logs parameters in the NEAR WASM virtual machine. ### Promises API Asynchronous cross-contract calls allow parallel execution of multiple contracts in parallel with subsequent aggregation on another contract. env exposes the following methods: - `promiseCreate(account_id: string, method_name: string, arguments: Uint8Array, amount: bigint, gas: bigint)` -- schedules an execution of a function on some contract; - `promiseThen(promise_index: bigint, account_id: string, method_name: string, arguments: Uint8Array, amount: bigint, gas: bigint)` -- attaches the callback back to the current contract once the function is executed; - `promiseAnd(...promise_idx: bigint)` -- combinator, allows waiting on several promises simultaneously, before executing the callback - `promiseBatchCreate(account_id: string)` -- create a NEAR promise which will have multiple promise actions inside - `promiseBatchThen(promise_index: bigint, account_id: string)` -- attach a callback NEAR promise to a batch of NEAR promise actions ### Promise API actions - `promiseBatchActionCreateAccount(promiseIndex: PromiseIndex)` -- Attach a create account promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionDeployContract(promiseIndex: PromiseIndex, code: Uint8Array)` -- Attach a deploy contract promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionFunctionCall(promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount)` -- Attach a function call promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionFunctionCallWeight(promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount, weight: GasWeight)` -- Attach a function call with weight promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionTransfer(promiseIndex: PromiseIndex, amount: NearAmount);` -- Attach a transfer promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionStake(promiseIndex: PromiseIndex, amount: NearAmount, publicKey: Uint8Array)` -- Attach a stake promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionAddKeyWithFullAccess(promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint)` -- Attach a add full access key promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionAddKeyWithFunctionCall(promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint, allowance: NearAmount, receiverId: string, methodNames: string)` -- Attach a add access key promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionDeleteKey(promiseIndex: PromiseIndex, publicKey: Uint8Array)` -- Attach a delete key promise action to the NEAR promise index with the provided promise index. - `promiseBatchActionDeleteAccount(promiseIndex: PromiseIndex, beneficiaryId: string)` -- Attach a delete account promise action to the NEAR promise index with the provided promise index. ### Promise API results - `promiseResultsCount()` -- Returns the number of promise results available. - `promiseResultRaw(promiseIndex: PromiseIndex)` -- Returns the result of the NEAR promise for the passed promise index. - `promiseResult(promiseIndex: PromiseIndex)` -- Returns the result of the NEAR promise for the passed promise index as utf-8 string. - `promiseReturn(promiseIndex: PromiseIndex)` -- Executes the promise in the NEAR WASM virtual machine. ### Storage API - `storageWriteRaw(key: Uint8Array, value: Uint8Array)` -- Writes the provided bytes to NEAR storage under the provided key. - `storageReadRaw(key: Uint8Array)` -- Reads the value from NEAR storage that is stored under the provided key. - `storageRemoveRaw(key: Uint8Array)` -- Removes the value of the provided key from NEAR storage. - `storageHasKeyRaw(key: Uint8Array)` -- Checks for the existence of a value under the provided key in NEAR storage. - `storageWrite(key: string, value: string)` -- Writes the provided utf-8 string to NEAR storage under the provided key. - `storageRead(key: string)` -- Reads the utf-8 string value from NEAR storage that is stored under the provided key. - `storageRemove(key: string)` -- Removes the value of the provided utf-8 string key from NEAR storage. - `storageHasKey(key: string)` -- Checks for the existence of a value under the provided utf-8 string key in NEAR storage. ### Validator API - `validatorStake(account_id: string)` -- Returns the number of staked NEAR of given validator, in yoctoNEAR. - `validatorTotalStake()` -- Returns the number of staked NEAR of all validators, in yoctoNEAR ### NearBindgen and other decorators You can write a simple smart contract by only using low-level APIs, such as `near.input()`, `near.storageRead()`, etc. In this case, the API of your contract will consist of all the exported JS functions. You can find an example of such a contract [here](https://github.com/near/near-sdk-js/blob/develop/examples/src/counter/counter-lowlevel.js). But if you want to build a more complex contracts with ease, you can use decorators from this SDK that will handle serialization, deserialization, and other boilerplate operations for you. In order to do that, your contract must be a class decorated with `@NearBindgen({})`. Each method in this class with `@call({})`, `@view({})`, and `@initialize({})` decorators will become functions of your smart contract. `call` functions can change state, and `view` functions can only read it. Your class must have a `constructor()`. You will not be able to call it, which is why it should not accept any parameters. You must declare all the parameters that you are planning to use in the constructor and set default values. The simplest example of the contract that follows all these rules can be found [here](https://github.com/near/near-sdk-js/blob/develop/examples/src/status-message/status-message.js) `NearBindgen` decorator can accept `requireInit parameter`. ```JS @NearBindgen({ requireInit: true }) class YourContract { ... } ``` It is `false` by default, but if you will set it to `true`, it will prevent all the `call` functions from being executed before you initialize the state of the contract. In order to initialize the contract, you need to add functions flagged with `@initialize({})` decorator. `@call({})` decorator can accept two parameters: `privateFunction` and `payableFunction`. They are both `false` by default. `privateFunction: true` can restrict access to this function to the contract itself. `payableFunction: true` will allow the function to accept payments (deposit). Without this flag, it will panic if any deposit was provided. ### Collections A few useful on-chain persistent collections are provided. All keys, values and elements are of type `string`. #### Vector Vector is an iterable implementation of vector that stores its content on the trie. Usage: ```js import {Vector} from 'near-sdk-js' // in contract class constructor: constructor() { super() this.v = new Vector('my_prefix_') } // Override the deserializer to load vector from chain deserialize() { super.deserialize() this.v = Object.assign(new Vector, this.v) } someMethod() { // insert this.v.push('abc') this.v.push('def') this.v.push('ghi') // batch insert, extend: this.v.extend(['xyz', '123']) // get let first = this.v.get(0) // remove, move the last element to the given index this.v.swapRemove(0) // replace this.v.replace(1, 'jkl') // remove the last this.v.pop() // len, isEmpty let len = this.v.length let isEmpty = this.v.isEmpty() // iterate for (let element of this.v) { near.log(element) } // toArray, convert to JavaScript Array let a = this.v.toArray() // clear ths.v.clear() } ``` #### LookupMap LookupMap is an non-iterable implementation of a map that stores its content directly on the trie. It's like a big hash map, but on trie. Usage: ```js import {LookupMap} from 'near-sdk-js' // in contract class constructor: constructor() { super() this.m = new LookupMap('prefix_a') } // Override the deserializer to load vector from chain deserialize() { super.deserialize() this.m = Object.assign(new LookupMap, this.m) } someMethod() { // insert this.m.set('abc', 'aaa') this.m.set('def', 'bbb') this.m.set('ghi', 'ccc') // batch insert, extend: this.m.extend([['xyz', '123'], ['key2', 'value2']]) // check exist let exist = this.m.containsKey('abc') // get let value = this.m.get('abc') // remove this.m.remove('def') // replace this.m.set('ghi', 'ddd') } ``` #### LookupSet LookupSet is an non-iterable implementation of a set that stores its content directly on the trie. It's like LookupMap, but it only stores whether the value presents. Usage: ```js import {LookupSet} from 'near-sdk-js' // in contract class constructor: constructor() { super() this.s = new LookupSet('prefix_b') } // Override the deserializer to load vector from chain deserialize() { super.deserialize() this.s = Object.assign(new LookupSet, this.s) } someMethod() { // insert this.s.set('abc') this.s.set('def') this.s.set('ghi') // batch insert, extend: this.s.extend(['xyz', '123']) // check exist let exist = this.s.contains('abc') // remove this.s.remove('def') } ``` #### UnorderedMap UnorderedMap is an iterable implementation of a map that stores its content directly on the trie. Usage: ```js import {UnorderedMap} from 'near-sdk-js' // in contract class constructor: constructor() { super() this.m = new UnorderedMap('prefix_c') } // Override the deserializer to load vector from chain deserialize() { super.deserialize() this.m.keys = Object.assign(new Vector, this.m.keys) this.m.values = Object.assign(new Vector, this.m.values) this.m = Object.assign(new UnorderedMap, this.m) } someMethod() { // insert this.m.set('abc', 'aaa') this.m.set('def', 'bbb') this.m.set('ghi', 'ccc') // batch insert, extend: this.m.extend([['xyz', '123'], ['key2', 'value2']]) // get let value = this.m.get('abc') // remove this.m.remove('def') // replace this.m.set('ghi', 'ddd') // len, isEmpty let len = this.m.length let isEmpty = this.m.isEmpty() // iterate for (let [k, v] of this.m) { near.log(k+v) } // toArray, convert to JavaScript Array let a = this.m.toArray() // clear this.m.clear() } ``` #### UnorderedSet UnorderedSet is an iterable implementation of a set that stores its content directly on the trie. It's like UnorderedMap but it only stores whether the value presents. Usage: ```js import {UnorderedSet} from 'near-sdk-js' // in contract class constructor: constructor() { super() this.s = new UnorderedSet('prefix_d') } // Override the deserializer to load vector from chain deserialize() { super.deserialize() this.s.elements = Object.assign(new Vector, this.s.elements) this.s = Object.assign(new UnorderedSet, this.s) } someMethod() { // insert this.s.set('abc') this.s.set('def') this.s.set('ghi') // batch insert, extend: this.s.extend(['xyz', '123']) // check exist let exist = this.s.contains('abc') // remove this.s.remove('def') // len, isEmpty let len = this.s.length let isEmpty = this.s.isEmpty() // iterate for (let e of this.s) { near.log(e) } // toArray, convert to JavaScript Array let a = this.s.toArray() // clear this.s.clear() } ``` ### High level Promise APIs Within a contract class that decorated by `@Nearbindgen`, you can work a high level JavaScript class, called `NearPromise`. It's equivalently expressive as promise batch APIs but much shorter to write and can be chained like a JavaScript Promise. In a `@call` method, you can return either a JavaScript value or a `NearPromise` object. In the later case, `@NearBindgen` will automatically `promiseReturn` it for you. Usage: ```js // create new promise import { NearPromise, near, includeBytes } from "near-sdk-js"; import { PublicKey } from "near-sdk-js/lib/types"; let promise = NearPromise.new("account-to-run-promise"); // possible promise actions, choose and chain what you need: promise .createAccount() .transfer(1_000_000_000_000_000_000_000_000_000_000_000_000n) .addFullAccessKey(new PublicKey(near.signerAccountPk())) .addAccessKey( new PublicKey(near.signerAccountPk()), 250000000000000000000000n, // allowance "receiver_account_id", "allowed_function_names" ) .stake(100000000000000000000000000000n, new PublicKey(near.signerAccountPk())) .deployContract(includeBytes("path/to/contract.wasm")) .functionCall( "callee_contract_account_id", inputArgs, 0, // amount 2 * Math.pow(10, 13) // gas ) .functionCallWeight( "callee_contract_account_id", inputArgs, 0, // amount 2 * Math.pow(10, 13), // gas 1 // weight ) .deleteKey(new PublicKey(near.signerAccountPk())) .deleteAccount("beneficial_account_id"); return promise; ``` In the case of deploy contract, `includeBytes` is a helpful build-time util. You can include the content of a wasm contract, by using `includeBytes('path/to/contract.wasm')`. In the case of `addFullAccessKey`, `addAccessKey` and `stake`, it takes a `PublicKey` object, you can find more details about it in the Types sections below. Besides above APIs to build something on top of an API, you can also chain promises with `.then` and `.and`, they're equivalent to promiseThen, promiseAnd: ```js // assume promise, promise2 and promise3 are create with above APIs, with several actions added like above. promise.and(promise2).then(promise3); // promiseAnd of [promise_id, promise2_id], then promiseThen(promise_and_id, promise3_id) return promise; ``` ### Types NEAR-SDK-JS also includes type definitions that are equivalent to that in Rust SDK / nearcore. You can browse them in near-sdk-js/src/types. Most of them are just type alias to string and bigint. #### Public Key Public Key is representing a NEAR account's public key in a JavaScript class. You can either initiate a Public Key from binary data, or from a human readable string. The binary data is in the same format as nearcore in `Uint8Array`. That's one byte to represent the curve type of the public key, either ed25519 (`0x0`), or secp256k1 (`0x1`), follows by the curve-specific public key data in bytes. Examples: ```js new PublicKey(near.signerAccountPk()); let pk = new PublicKey( new Uint8Array([ // CurveType.ED25519 = 0 0, // ED25519 PublicKey data 186, 44, 216, 49, 157, 48, 151, 47, 23, 244, 137, 69, 78, 150, 54, 42, 30, 248, 110, 26, 205, 18, 137, 154, 10, 208, 26, 183, 65, 166, 223, 18, ]) ); let pk = new PublicKey( new Uint8Array([ // CurveType.SECP256K1 = 1 1, // SECP256K1 PublicKey data 242, 86, 198, 230, 200, 11, 33, 63, 42, 160, 176, 23, 68, 35, 93, 81, 92, 89, 68, 53, 190, 101, 27, 21, 136, 58, 16, 221, 71, 47, 166, 70, 206, 98, 234, 243, 103, 13, 197, 203, 145, 0, 160, 202, 42, 85, 178, 193, 71, 193, 233, 163, 140, 228, 40, 135, 142, 125, 70, 225, 251, 113, 74, 153, ]) ); ``` The human readable form is `ed25519:` or `secp256k1:` following base58-encoded public key. And initialize the Public Key with `PublicKey.fromString`: ```js PublicKey.fromString('ed25519:DXkVZkHd7WUUejCK7i74uAoZWy1w9AZqshhTHxhmqHuB`) PublicKey.fromString('secp256k1:5r22SrjrDvgY3wdQsnjgxkeAbU1VcM71FYvALEQWihjM3Xk4Be1CpETTqFccChQr4iJwDroSDVmgaWZv2AcXvYeL`) ``` Once a PublicKey object is created, it can be used in high level promise APIs that takes a public key, such as `addFullAccessKey`, `addAccessKey` and `stake`. ## How to use NEAR SDK JS on Windows You can develop smart contracts on Windows using Windows Subsystem for Linux (WSL2). In order to use WSL2, follow the next steps: - Run `PowerShell` as Administrator - Execute `wsl --install` to install Ubuntu and do additional setup automatically. Check more details [here](https://learn.microsoft.com/en-us/windows/wsl/install) - Restart your machine - `WSL2` will continue setup process on start. Setup your username and password when prompted. - Check [this](https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-wsl) guide to setup `npm`, `node`, `npx`, `VSCode` and other tools of your choice in order to start developing. In case of any issues of setting up WSL2 make sure that: - Your Windows OS is up to date - Virtualization is turned on in BIOS - `Windows Subsystem for Linux` and `Virtual Machine Platform` are turned on in `Windows Features` (Start -> Search -> Turn Windows Feature On or Off) ================================================ FILE: packages/near-sdk-js/builder/builder.c ================================================ #include #include "../node_modules/near-sdk-js/lib/cli/deps/quickjs/quickjs-libc-min.h" #include "../node_modules/near-sdk-js/lib/cli/deps/quickjs/libbf.h" #include "code.h" static JSContext *JS_NewCustomContext(JSRuntime *rt) { JSContext *ctx = JS_NewContextRaw(rt); if (!ctx) return NULL; JS_AddIntrinsicBaseObjects(ctx); JS_AddIntrinsicDate(ctx); JS_AddIntrinsicEval(ctx); JS_AddIntrinsicStringNormalize(ctx); JS_AddIntrinsicRegExp(ctx); JS_AddIntrinsicJSON(ctx); JS_AddIntrinsicProxy(ctx); JS_AddIntrinsicMapSet(ctx); JS_AddIntrinsicTypedArrays(ctx); JS_AddIntrinsicPromise(ctx); JS_AddIntrinsicBigInt(ctx); return ctx; } #define DEFINE_NEAR_METHOD(name) \ void name () __attribute__((export_name(#name))) {\ JSRuntime *rt;\ JSContext *ctx;\ JSValue mod_obj, fun_obj, result, error, error_message, error_stack;\ const char *error_message_c, *error_stack_c;\ char *error_c;\ size_t msg_len, stack_len;\ rt = JS_NewRuntime();\ ctx = JS_NewCustomContext(rt);\ js_add_near_host_functions(ctx);\ mod_obj = js_load_module_binary(ctx, code, code_size);\ fun_obj = JS_GetProperty(ctx, mod_obj, JS_NewAtom(ctx, #name));\ result = JS_Call(ctx, fun_obj, mod_obj, 0, NULL);\ if (JS_IsException(result)) {\ error = JS_GetException(ctx);\ error_message = JS_GetPropertyStr(ctx, error, "message");\ error_stack = JS_GetPropertyStr(ctx, error, "stack");\ error_message_c = JS_ToCStringLen(ctx, &msg_len, error_message);\ error_stack_c = JS_ToCStringLen(ctx, &stack_len, error_stack);\ error_c = malloc(msg_len+1+stack_len);\ strncpy(error_c, error_message_c, msg_len);\ error_c[msg_len] = '\n';\ strncpy(error_c+msg_len+1, error_stack_c, stack_len);\ panic_utf8(msg_len+1+stack_len, (uint64_t)error_c);\ }\ js_std_loop(ctx);\ } // ############# // # Registers # // ############# extern void read_register(uint64_t register_id, uint64_t ptr); extern uint64_t register_len(uint64_t register_id); extern void write_register(uint64_t register_id, uint64_t data_len, uint64_t data_ptr); // ############### // # Context API # // ############### extern void current_account_id(uint64_t register_id); extern void signer_account_id(uint64_t register_id); extern void signer_account_pk(uint64_t register_id); extern void predecessor_account_id(uint64_t register_id); extern void input(uint64_t register_id); extern uint64_t block_index(); extern uint64_t block_timestamp(); extern uint64_t epoch_height(); extern uint64_t storage_usage(); // ################# // # Economics API # // ################# extern void account_balance(uint64_t balance_ptr); extern void account_locked_balance(uint64_t balance_ptr); extern void attached_deposit(uint64_t balance_ptr); extern uint64_t prepaid_gas(); extern uint64_t used_gas(); // ############ // # Math API # // ############ extern void random_seed(uint64_t register_id); extern void sha256(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern void keccak256(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern void keccak512(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern void ripemd160(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern uint64_t ecrecover(uint64_t hash_len, uint64_t hash_ptr, uint64_t sign_len, uint64_t sig_ptr, uint64_t v, uint64_t malleability_flag, uint64_t register_id); // ##################### // # Miscellaneous API # // ##################### extern void value_return(uint64_t value_len, uint64_t value_ptr); extern void panic(void); extern void panic_utf8(uint64_t len, uint64_t ptr); extern void log_utf8(uint64_t len, uint64_t ptr); extern void log_utf16(uint64_t len, uint64_t ptr); // Name confliction with WASI. Can be re-exported with a different name on NEAR side with a protocol upgrade // Or, this is actually not a primitive, can be implement with log and panic host functions in C side or JS side. // extern void abort(uint32_t msg_ptr, uint32_t filename_ptr, uint32_t u32, uint32_t col); // ################ // # Promises API # // ################ extern uint64_t promise_create(uint64_t account_id_len, uint64_t account_id_ptr, uint64_t method_name_len, uint64_t method_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas); extern uint64_t promise_then(uint64_t promise_index, uint64_t account_id_len, uint64_t account_id_ptr, uint64_t method_name_len, uint64_t method_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas); extern uint64_t promise_and(uint64_t promise_idx_ptr, uint64_t promise_idx_count); extern uint64_t promise_batch_create(uint64_t account_id_len, uint64_t account_id_ptr); extern uint64_t promise_batch_then(uint64_t promise_index, uint64_t account_id_len, uint64_t account_id_ptr); // ####################### // # Promise API actions # // ####################### extern void promise_batch_action_create_account(uint64_t promise_index); extern void promise_batch_action_deploy_contract(uint64_t promise_index, uint64_t code_len, uint64_t code_ptr); extern void promise_batch_action_function_call(uint64_t promise_index, uint64_t method_name_len, uint64_t method_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas); extern void promise_batch_action_transfer(uint64_t promise_index, uint64_t amount_ptr); extern void promise_batch_action_stake(uint64_t promise_index, uint64_t amount_ptr, uint64_t public_key_len, uint64_t public_key_ptr); extern void promise_batch_action_add_key_with_full_access(uint64_t promise_index, uint64_t public_key_len, uint64_t public_key_ptr, uint64_t nonce); extern void promise_batch_action_add_key_with_function_call(uint64_t promise_index, uint64_t public_key_len, uint64_t public_key_ptr, uint64_t nonce, uint64_t allowance_ptr, uint64_t receiver_id_len, uint64_t receiver_id_ptr, uint64_t method_names_len, uint64_t method_names_ptr); extern void promise_batch_action_delete_key(uint64_t promise_index, uint64_t public_key_len, uint64_t public_key_ptr); extern void promise_batch_action_delete_account(uint64_t promise_index, uint64_t beneficiary_id_len, uint64_t beneficiary_id_ptr); extern void promise_batch_action_function_call_weight(uint64_t promise_index, uint64_t function_name_len, uint64_t function_name_ptr, uint64_t arguments_len, uint64_t arguments_ptr, uint64_t amount_ptr, uint64_t gas, uint64_t weight); // ####################### // # Promise API results # // ####################### extern uint64_t promise_results_count(void); extern uint64_t promise_result(uint64_t result_idx, uint64_t register_id); extern void promise_return(uint64_t promise_idx); // ############### // # Storage API # // ############### extern uint64_t storage_write(uint64_t key_len, uint64_t key_ptr, uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern uint64_t storage_read(uint64_t key_len, uint64_t key_ptr, uint64_t register_id); extern uint64_t storage_remove(uint64_t key_len, uint64_t key_ptr, uint64_t register_id); extern uint64_t storage_has_key(uint64_t key_len, uint64_t key_ptr); // ################# // # Validator API # // ################# extern void validator_stake(uint64_t account_id_len, uint64_t account_id_ptr, uint64_t stake_ptr); extern void validator_total_stake(uint64_t stake_ptr); // ############# // # Alt BN128 # // ############# extern void alt_bn128_g1_multiexp(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern void alt_bn128_g1_sum(uint64_t value_len, uint64_t value_ptr, uint64_t register_id); extern uint64_t alt_bn128_pairing_check(uint64_t value_len, uint64_t value_ptr); static uint8_t* JS_Uint8Array_to_C(JSContext *ctx, JSValue array, size_t *len) { uint8_t *ptr; JSValue buffer; size_t pbyte_offset, psize, pbytes_per_element = 0; buffer = JS_GetTypedArrayBuffer(ctx, array, &pbyte_offset, len, &pbytes_per_element); if (JS_IsException(buffer) || pbytes_per_element != 1) { return NULL; } ptr = JS_GetArrayBuffer(ctx, &psize, buffer); if (ptr == NULL) { return NULL; } return ptr + pbyte_offset; } static JSValue near_read_register(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data; uint64_t data_len; JSValue arraybuffer, ret; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } data_len = register_len(register_id); if (data_len != UINT64_MAX) { data = malloc(data_len); read_register(register_id, (uint64_t)data); arraybuffer = JS_NewArrayBuffer(ctx, data, (size_t)data_len, NULL, NULL, TRUE); return JS_CallConstructor(ctx, JS_GetPropertyStr(ctx, JS_GetGlobalObject(ctx), "Uint8Array"), 1, (JSValueConst *)&arraybuffer); } else { return JS_UNDEFINED; } } static JSValue near_register_len(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id, len; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } len = register_len(register_id); return JS_NewBigUint64(ctx, len); } static JSValue near_write_register(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } data_ptr = JS_Uint8Array_to_C(ctx, argv[1], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } write_register(register_id, data_len, (uint64_t)data_ptr); return JS_UNDEFINED; } static JSValue near_current_account_id(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } current_account_id(register_id); return JS_UNDEFINED; } static JSValue near_signer_account_id(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } signer_account_id(register_id); return JS_UNDEFINED; } static JSValue near_signer_account_pk(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } signer_account_pk(register_id); return JS_UNDEFINED; } static JSValue near_predecessor_account_id(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } predecessor_account_id(register_id); return JS_UNDEFINED; } static JSValue near_input(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } input(register_id); return JS_UNDEFINED; } static JSValue near_block_index(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = block_index(); return JS_NewBigUint64(ctx, value); } static JSValue near_block_timestamp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = block_timestamp(); return JS_NewBigUint64(ctx, value); } static JSValue near_epoch_height(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = epoch_height(); return JS_NewBigUint64(ctx, value); } static JSValue near_storage_usage(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = storage_usage(); return JS_NewBigUint64(ctx, value); } // ptr[0] ptr[1] is little-endian u128. static JSValue u128_to_quickjs(JSContext *ctx, uint64_t* ptr) { JSValue value; bf_t* bn; bf_t b; value = JS_NewBigInt(ctx); bn = JS_GetBigInt(value); // from ptr[] to bn // high 64 bits bf_set_ui(bn, ptr[1]); bf_mul_2exp(bn, 64, BF_PREC_INF, BF_RNDZ); // low 64 bits bf_init(bn->ctx, &b); bf_set_ui(&b, ptr[0]); bf_add(bn, bn, &b, BF_PREC_INF, BF_RNDZ); bf_delete(&b); return value; } static int quickjs_bigint_to_u128(JSContext *ctx, JSValueConst val, uint64_t* ptr) { bf_t* a; bf_t q, r, b, one, u128max; a = JS_GetBigInt(val); bf_init(a->ctx, &u128max); bf_set_ui(&u128max, 1); bf_mul_2exp(&u128max, 128, BF_PREC_INF, BF_RNDZ); if (bf_cmp_le(&u128max, a)) { return 1; } bf_init(a->ctx, &q); bf_init(a->ctx, &r); bf_init(a->ctx, &b); bf_init(a->ctx, &one); bf_set_ui(&b, UINT64_MAX); bf_set_ui(&one, 1); bf_add(&b, &b, &one, BF_PREC_INF, BF_RNDZ); bf_divrem(&q, &r, a, &b, BF_PREC_INF, BF_RNDZ, BF_RNDZ); bf_get_uint64(ptr, &r); bf_get_uint64(ptr+1, &q); return 0; } static int quickjs_int_to_u128(JSContext *ctx, JSValueConst val, uint64_t* ptr) { if (JS_ToUint64Ext(ctx, ptr, val) < 0) { return 1; } ptr[1] = 0; return 0; } static int quickjs_to_u128(JSContext *ctx, JSValueConst val, uint64_t* ptr) { if (JS_IsBigInt(ctx, val)) return quickjs_bigint_to_u128(ctx, val, ptr); else { return quickjs_int_to_u128(ctx, val, ptr); } } static JSValue near_account_balance(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t ptr[2]; account_balance((uint64_t)ptr); return u128_to_quickjs(ctx, ptr); } static JSValue near_account_locked_balance(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t ptr[2]; account_locked_balance((uint64_t)ptr); return u128_to_quickjs(ctx, ptr); } static JSValue near_attached_deposit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t ptr[2]; attached_deposit((uint64_t)ptr); return u128_to_quickjs(ctx, ptr); } static JSValue near_prepaid_gas(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = prepaid_gas(); return JS_NewBigUint64(ctx, value); } static JSValue near_used_gas(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = used_gas(); return JS_NewBigUint64(ctx, value); } static JSValue near_random_seed(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; if (JS_ToUint64Ext(ctx, ®ister_id, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } random_seed(register_id); return JS_UNDEFINED; } static JSValue near_sha256(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } sha256(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_keccak256(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } keccak256(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_keccak512(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } keccak512(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_ripemd160(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } ripemd160(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_ecrecover(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t malleability_flag, v, register_id, result; uint8_t *hash_ptr, *sig_ptr; size_t hash_len, sign_len; hash_ptr = JS_Uint8Array_to_C(ctx, argv[0], &hash_len); if (hash_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for hash"); } sig_ptr = JS_Uint8Array_to_C(ctx, argv[1], &sign_len); if (sig_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for sig"); } if (JS_ToUint64Ext(ctx, &malleability_flag, argv[2]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for malleability_flag"); } if (JS_ToUint64Ext(ctx, &v, argv[3]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for v"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[4]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } result = ecrecover(hash_len, (uint64_t)hash_ptr, sign_len, (uint64_t)sig_ptr, malleability_flag, v, register_id); return JS_NewBigUint64(ctx, result); } static JSValue near_value_return(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *value_ptr; size_t value_len; value_ptr = JS_Uint8Array_to_C(ctx, argv[0], &value_len); if (value_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for value"); } value_return(value_len, (uint64_t)(value_ptr)); return JS_UNDEFINED; } static JSValue near_panic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *data_ptr; size_t data_len; if (argc == 1) { data_ptr = JS_ToCStringLen(ctx, &data_len, argv[0]); panic_utf8(data_len, (uint64_t)data_ptr); } else { panic(); } return JS_UNDEFINED; } static JSValue near_panic_utf8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for message"); } panic_utf8(data_len, (uint64_t)data_ptr); return JS_UNDEFINED; } static JSValue near_log(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *data_ptr; size_t data_len; data_ptr = JS_ToCStringLen(ctx, &data_len, argv[0]); log_utf8(data_len, (uint64_t)data_ptr); return JS_UNDEFINED; } static JSValue near_log_utf8(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for message"); } log_utf8(data_len, (uint64_t)data_ptr); return JS_UNDEFINED; } static JSValue near_log_utf16(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for message"); } log_utf16(data_len, (uint64_t)data_ptr); return JS_UNDEFINED; } static JSValue near_promise_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *account_id_ptr, *method_name_ptr; uint8_t *arguments_ptr; size_t account_id_len, method_name_len, arguments_len; uint64_t amount_ptr[2]; // amount is u128 uint64_t gas, ret; account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[0]); method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[1]); arguments_ptr = JS_Uint8Array_to_C(ctx, argv[2], &arguments_len); if (arguments_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for arguments"); } if (quickjs_to_u128(ctx, argv[3], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } if (JS_ToUint64Ext(ctx, &gas, argv[4]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); } ret = promise_create(account_id_len, (uint64_t)account_id_ptr, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_then(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *account_id_ptr, *method_name_ptr; uint8_t *arguments_ptr; size_t account_id_len, method_name_len, arguments_len; uint64_t amount_ptr[2]; // amount is u128 uint64_t gas, ret; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[1]); method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[2]); arguments_ptr = JS_Uint8Array_to_C(ctx, argv[3], &arguments_len); if (arguments_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for arguments"); } if (quickjs_to_u128(ctx, argv[4], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } if (JS_ToUint64Ext(ctx, &gas, argv[5]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); } ret = promise_then(promise_index, account_id_len, (uint64_t)account_id_ptr, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_and(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_idx_ptr[argc], ret; for(int i = 0; i < argc; i++) { if (JS_ToUint64Ext(ctx, &promise_idx_ptr[i], argv[i]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_id"); } } ret = promise_and((uint64_t)promise_idx_ptr, argc); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_batch_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *account_id_ptr; size_t account_id_len; uint64_t ret; account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[0]); ret = promise_batch_create(account_id_len, (uint64_t)account_id_ptr); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_batch_then(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *account_id_ptr; size_t account_id_len; uint64_t ret; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[1]); ret = promise_batch_then(promise_index, account_id_len, (uint64_t)account_id_ptr); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_batch_action_create_account(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } promise_batch_action_create_account(promise_index); return JS_UNDEFINED; } static JSValue near_promise_batch_action_deploy_contract(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; uint8_t *code_ptr; size_t code_len; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } code_ptr = JS_Uint8Array_to_C(ctx, argv[1], &code_len); if (code_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for code"); } promise_batch_action_deploy_contract(promise_index, code_len, (uint64_t)code_ptr); return JS_UNDEFINED; } static JSValue near_promise_batch_action_function_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *method_name_ptr; uint8_t *arguments_ptr; size_t method_name_len, arguments_len; uint64_t amount_ptr[2]; // amount is u128 uint64_t gas; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[1]); arguments_ptr = JS_Uint8Array_to_C(ctx, argv[2], &arguments_len); if (arguments_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for arguments"); } if (quickjs_to_u128(ctx, argv[3], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } if (JS_ToUint64Ext(ctx, &gas, argv[4]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); } promise_batch_action_function_call(promise_index, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas); return JS_UNDEFINED; } static JSValue near_promise_batch_action_transfer(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; uint64_t amount_ptr[2]; // amount is u128 if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } if (quickjs_to_u128(ctx, argv[1], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } promise_batch_action_transfer(promise_index, (uint64_t)amount_ptr); return JS_UNDEFINED; } static JSValue near_promise_batch_action_stake(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; uint64_t amount_ptr[2]; uint8_t *public_key_ptr; size_t public_key_len; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } if (quickjs_to_u128(ctx, argv[1], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } public_key_ptr = JS_Uint8Array_to_C(ctx, argv[2], &public_key_len); if (public_key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for public key"); } promise_batch_action_stake(promise_index, (uint64_t)amount_ptr, public_key_len, (uint64_t)public_key_ptr); return JS_UNDEFINED; } static JSValue near_promise_batch_action_add_key_with_full_access(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; uint8_t *public_key_ptr; size_t public_key_len; uint64_t nonce; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } public_key_ptr = JS_Uint8Array_to_C(ctx, argv[1], &public_key_len); if (public_key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for public key"); } if (JS_ToUint64Ext(ctx, &nonce, argv[2]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for nonce"); } promise_batch_action_add_key_with_full_access(promise_index, public_key_len, (uint64_t)public_key_ptr, nonce); return JS_UNDEFINED; } static JSValue near_promise_batch_action_add_key_with_function_call(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *receiver_id_ptr, *method_names_ptr; uint8_t *public_key_ptr; size_t public_key_len, receiver_id_len, method_names_len; uint64_t nonce, allowance_ptr[2]; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } public_key_ptr = JS_Uint8Array_to_C(ctx, argv[1], &public_key_len); if (public_key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for public key"); } if (JS_ToUint64Ext(ctx, &nonce, argv[2]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for nonce"); } if (quickjs_to_u128(ctx, argv[3], allowance_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for allowance"); } receiver_id_ptr = JS_ToCStringLen(ctx, &receiver_id_len, argv[4]); method_names_ptr = JS_ToCStringLen(ctx, &method_names_len, argv[5]); promise_batch_action_add_key_with_function_call(promise_index, public_key_len, (uint64_t)public_key_ptr, nonce, (uint64_t)allowance_ptr, receiver_id_len, (uint64_t)receiver_id_ptr, method_names_len, (uint64_t)method_names_ptr); return JS_UNDEFINED; } static JSValue near_promise_batch_action_delete_key(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; uint8_t *public_key_ptr; size_t public_key_len; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } public_key_ptr = JS_Uint8Array_to_C(ctx, argv[1], &public_key_len); if (public_key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for public key"); } promise_batch_action_delete_key(promise_index, public_key_len, (uint64_t)public_key_ptr); return JS_UNDEFINED; } static JSValue near_promise_batch_action_function_call_weight(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *method_name_ptr, *arguments_ptr; size_t method_name_len, arguments_len; uint64_t amount_ptr[2]; // amount is u128 uint64_t gas; uint64_t weight; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } method_name_ptr = JS_ToCStringLen(ctx, &method_name_len, argv[1]); arguments_ptr = JS_ToCStringLenRaw(ctx, &arguments_len, argv[2]); if (quickjs_to_u128(ctx, argv[3], amount_ptr) != 0) { return JS_ThrowTypeError(ctx, "Expect Uint128 for amount"); } if (JS_ToUint64Ext(ctx, &gas, argv[4]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for gas"); } if (JS_ToUint64Ext(ctx, &weight, argv[5]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for weight"); } promise_batch_action_function_call_weight(promise_index, method_name_len, (uint64_t)method_name_ptr, arguments_len, (uint64_t)arguments_ptr, (uint64_t)amount_ptr, gas, weight); return JS_UNDEFINED; } static JSValue near_promise_batch_action_delete_account(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_index; const char *beneficiary_id_ptr; size_t beneficiary_id_len; if (JS_ToUint64Ext(ctx, &promise_index, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_index"); } beneficiary_id_ptr = JS_ToCStringLen(ctx, &beneficiary_id_len, argv[1]); promise_batch_action_delete_account(promise_index, beneficiary_id_len, (uint64_t)beneficiary_id_ptr); return JS_UNDEFINED; } static JSValue near_promise_results_count(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t value; value = promise_results_count(); return JS_NewBigUint64(ctx, value); } static JSValue near_promise_result(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t result_idx, register_id; uint64_t ret; if (JS_ToUint64Ext(ctx, &result_idx, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for result_idx"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } ret = promise_result(result_idx, register_id); return JS_NewBigUint64(ctx, ret); } static JSValue near_promise_return(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t promise_idx; if (JS_ToUint64Ext(ctx, &promise_idx, argv[0]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for promise_idx"); } promise_return(promise_idx); return JS_UNDEFINED; } static JSValue near_storage_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *key_ptr, *value_ptr; size_t key_len, value_len; uint64_t register_id, ret; key_ptr = JS_Uint8Array_to_C(ctx, argv[0], &key_len); if (key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for key"); } value_ptr = JS_Uint8Array_to_C(ctx, argv[1], &value_len); if (value_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for value"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[2]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } ret = storage_write(key_len, (uint64_t)key_ptr, value_len, (uint64_t)value_ptr, register_id); return JS_NewBigUint64(ctx, ret); } static JSValue near_storage_read(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *key_ptr; size_t key_len; uint64_t register_id; uint64_t ret; key_ptr = JS_Uint8Array_to_C(ctx, argv[0], &key_len); if (key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for key"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } ret = storage_read(key_len, (uint64_t)key_ptr, register_id); return JS_NewBigUint64(ctx, ret); } static JSValue near_storage_remove(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *key_ptr; size_t key_len; uint64_t register_id; uint64_t ret; key_ptr = JS_Uint8Array_to_C(ctx, argv[0], &key_len); if (key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for key"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } ret = storage_remove(key_len, (uint64_t)key_ptr, register_id); return JS_NewBigUint64(ctx, ret); } static JSValue near_storage_has_key(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *key_ptr; size_t key_len; uint64_t ret; key_ptr = JS_Uint8Array_to_C(ctx, argv[0], &key_len); if (key_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for key"); } ret = storage_has_key(key_len, (uint64_t)key_ptr); return JS_NewBigUint64(ctx, ret); } static JSValue near_validator_stake(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *account_id_ptr; size_t account_id_len; uint64_t stake_ptr[2]; account_id_ptr = JS_ToCStringLen(ctx, &account_id_len, argv[0]); validator_stake(account_id_len, (uint64_t)account_id_ptr, (uint64_t)stake_ptr); return u128_to_quickjs(ctx, stake_ptr); } static JSValue near_validator_total_stake(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t stake_ptr[2]; validator_total_stake((uint64_t)stake_ptr); return u128_to_quickjs(ctx, stake_ptr); } static JSValue near_utf8_string_to_uint8array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *data; size_t len; JSValue arraybuffer; data = JS_ToCStringLen(ctx, &len, argv[0]); arraybuffer = JS_NewArrayBuffer(ctx, (uint8_t *)data, len, NULL, NULL, TRUE); return JS_CallConstructor(ctx, JS_GetPropertyStr(ctx, JS_GetGlobalObject(ctx), "Uint8Array"), 1, (JSValueConst *)&arraybuffer); } static JSValue near_latin1_string_to_uint8array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { const char *data; size_t len; JSValue arraybuffer; data = JS_ToCStringLenRaw(ctx, &len, argv[0]); arraybuffer = JS_NewArrayBuffer(ctx, (uint8_t *)data, len, NULL, NULL, TRUE); return JS_CallConstructor(ctx, JS_GetPropertyStr(ctx, JS_GetGlobalObject(ctx), "Uint8Array"), 1, (JSValueConst *)&arraybuffer); } static JSValue near_uint8array_to_latin1_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array"); } return JS_NewStringLenRaw(ctx, (const char *)data_ptr, data_len); } static JSValue near_uint8array_to_utf8_string(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array"); } return JS_NewStringLen(ctx, (const char *)data_ptr, data_len); } static JSValue near_alt_bn128_g1_multiexp(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } alt_bn128_g1_multiexp(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_alt_bn128_g1_sum(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint64_t register_id; uint8_t *data_ptr; size_t data_len; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } if (JS_ToUint64Ext(ctx, ®ister_id, argv[1]) < 0) { return JS_ThrowTypeError(ctx, "Expect Uint64 for register_id"); } alt_bn128_g1_sum(data_len, (uint64_t)data_ptr, register_id); return JS_UNDEFINED; } static JSValue near_alt_bn128_pairing_check(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { uint8_t *data_ptr; size_t data_len; uint64_t ret; data_ptr = JS_Uint8Array_to_C(ctx, argv[0], &data_len); if (data_ptr == NULL) { return JS_ThrowTypeError(ctx, "Expect Uint8Array for data"); } ret = alt_bn128_pairing_check(data_len, (uint64_t)data_ptr); return JS_NewBigUint64(ctx, ret); } static void js_add_near_host_functions(JSContext* ctx) { JSValue global_obj, env; global_obj = JS_GetGlobalObject(ctx); env = JS_NewObject(ctx); JS_SetPropertyStr(ctx, env, "read_register", JS_NewCFunction(ctx, near_read_register, "read_register", 1)); JS_SetPropertyStr(ctx, env, "register_len", JS_NewCFunction(ctx, near_register_len, "register_len", 1)); JS_SetPropertyStr(ctx, env, "write_register", JS_NewCFunction(ctx, near_write_register, "write_register", 2)); JS_SetPropertyStr(ctx, env, "current_account_id", JS_NewCFunction(ctx, near_current_account_id, "current_account_id", 1)); JS_SetPropertyStr(ctx, env, "signer_account_id", JS_NewCFunction(ctx, near_signer_account_id, "signer_account_id", 1)); JS_SetPropertyStr(ctx, env, "signer_account_pk", JS_NewCFunction(ctx, near_signer_account_pk, "signer_account_pk", 1)); JS_SetPropertyStr(ctx, env, "predecessor_account_id", JS_NewCFunction(ctx, near_predecessor_account_id, "predecessor_account_id", 1)); JS_SetPropertyStr(ctx, env, "input", JS_NewCFunction(ctx, near_input, "input", 1)); JS_SetPropertyStr(ctx, env, "block_index", JS_NewCFunction(ctx, near_block_index, "block_index", 0)); JS_SetPropertyStr(ctx, env, "block_timestamp", JS_NewCFunction(ctx, near_block_timestamp, "block_timestamp", 0)); JS_SetPropertyStr(ctx, env, "epoch_height", JS_NewCFunction(ctx, near_epoch_height, "epoch_height", 0)); JS_SetPropertyStr(ctx, env, "storage_usage", JS_NewCFunction(ctx, near_storage_usage, "storage_usage", 0)); JS_SetPropertyStr(ctx, env, "account_balance", JS_NewCFunction(ctx, near_account_balance, "account_balance", 0)); JS_SetPropertyStr(ctx, env, "account_locked_balance", JS_NewCFunction(ctx, near_account_locked_balance, "account_locked_balance", 0)); JS_SetPropertyStr(ctx, env, "attached_deposit", JS_NewCFunction(ctx, near_attached_deposit, "attached_deposit", 0)); JS_SetPropertyStr(ctx, env, "prepaid_gas", JS_NewCFunction(ctx, near_prepaid_gas, "prepaid_gas", 0)); JS_SetPropertyStr(ctx, env, "used_gas", JS_NewCFunction(ctx, near_used_gas, "used_gas", 0)); JS_SetPropertyStr(ctx, env, "random_seed", JS_NewCFunction(ctx, near_random_seed, "random_seed", 1)); JS_SetPropertyStr(ctx, env, "sha256", JS_NewCFunction(ctx, near_sha256, "sha256", 2)); JS_SetPropertyStr(ctx, env, "keccak256", JS_NewCFunction(ctx, near_keccak256, "keccak256", 2)); JS_SetPropertyStr(ctx, env, "keccak512", JS_NewCFunction(ctx, near_keccak512, "keccak512", 2)); JS_SetPropertyStr(ctx, env, "ripemd160", JS_NewCFunction(ctx, near_ripemd160, "ripemd160", 2)); JS_SetPropertyStr(ctx, env, "ecrecover", JS_NewCFunction(ctx, near_ecrecover, "ecrecover", 5)); JS_SetPropertyStr(ctx, env, "value_return", JS_NewCFunction(ctx, near_value_return, "value_return", 1)); JS_SetPropertyStr(ctx, env, "panic", JS_NewCFunction(ctx, near_panic, "panic", 1)); JS_SetPropertyStr(ctx, env, "panic_utf8", JS_NewCFunction(ctx, near_panic_utf8, "panic_utf8", 1)); JS_SetPropertyStr(ctx, env, "log", JS_NewCFunction(ctx, near_log, "log", 1)); JS_SetPropertyStr(ctx, env, "log_utf8", JS_NewCFunction(ctx, near_log_utf8, "log_utf8", 1)); JS_SetPropertyStr(ctx, env, "log_utf16", JS_NewCFunction(ctx, near_log_utf16, "log_utf16", 1)); JS_SetPropertyStr(ctx, env, "promise_create", JS_NewCFunction(ctx, near_promise_create, "promise_create", 5)); JS_SetPropertyStr(ctx, env, "promise_then", JS_NewCFunction(ctx, near_promise_then, "promise_then", 6)); JS_SetPropertyStr(ctx, env, "promise_and", JS_NewCFunction(ctx, near_promise_and, "promise_and", 1)); JS_SetPropertyStr(ctx, env, "promise_batch_create", JS_NewCFunction(ctx, near_promise_batch_create, "promise_batch_create", 1)); JS_SetPropertyStr(ctx, env, "promise_batch_then", JS_NewCFunction(ctx, near_promise_batch_then, "promise_batch_then", 2)); JS_SetPropertyStr(ctx, env, "promise_batch_action_create_account", JS_NewCFunction(ctx, near_promise_batch_action_create_account, "promise_batch_action_create_account", 1)); JS_SetPropertyStr(ctx, env, "promise_batch_action_deploy_contract", JS_NewCFunction(ctx, near_promise_batch_action_deploy_contract, "promise_batch_action_deploy_contract", 2)); JS_SetPropertyStr(ctx, env, "promise_batch_action_function_call", JS_NewCFunction(ctx, near_promise_batch_action_function_call, "promise_batch_action_function_call", 5)); JS_SetPropertyStr(ctx, env, "promise_batch_action_transfer", JS_NewCFunction(ctx, near_promise_batch_action_transfer, "promise_batch_action_transfer", 2)); JS_SetPropertyStr(ctx, env, "promise_batch_action_stake", JS_NewCFunction(ctx, near_promise_batch_action_stake, "promise_batch_action_stake", 3)); JS_SetPropertyStr(ctx, env, "promise_batch_action_add_key_with_full_access", JS_NewCFunction(ctx, near_promise_batch_action_add_key_with_full_access, "promise_batch_action_add_key_with_full_access", 3)); JS_SetPropertyStr(ctx, env, "promise_batch_action_add_key_with_function_call", JS_NewCFunction(ctx, near_promise_batch_action_add_key_with_function_call, "promise_batch_action_add_key_with_function_call", 6)); JS_SetPropertyStr(ctx, env, "promise_batch_action_delete_key", JS_NewCFunction(ctx, near_promise_batch_action_delete_key, "promise_batch_action_delete_key", 2)); JS_SetPropertyStr(ctx, env, "promise_batch_action_delete_account", JS_NewCFunction(ctx, near_promise_batch_action_delete_account, "promise_batch_action_delete_account", 2)); JS_SetPropertyStr(ctx, env, "promise_batch_action_function_call_weight", JS_NewCFunction(ctx, near_promise_batch_action_function_call_weight, "promise_batch_action_function_call_weight", 6)); JS_SetPropertyStr(ctx, env, "promise_results_count", JS_NewCFunction(ctx, near_promise_results_count, "promise_results_count", 0)); JS_SetPropertyStr(ctx, env, "promise_result", JS_NewCFunction(ctx, near_promise_result, "promise_result", 2)); JS_SetPropertyStr(ctx, env, "promise_return", JS_NewCFunction(ctx, near_promise_return, "promise_return", 1)); JS_SetPropertyStr(ctx, env, "storage_write", JS_NewCFunction(ctx, near_storage_write, "storage_write", 2)); JS_SetPropertyStr(ctx, env, "storage_read", JS_NewCFunction(ctx, near_storage_read, "storage_read", 2)); JS_SetPropertyStr(ctx, env, "storage_remove", JS_NewCFunction(ctx, near_storage_remove, "storage_remove", 2)); JS_SetPropertyStr(ctx, env, "storage_has_key", JS_NewCFunction(ctx, near_storage_has_key, "storage_has_key", 2)); JS_SetPropertyStr(ctx, env, "validator_stake", JS_NewCFunction(ctx, near_validator_stake, "validator_stake", 2)); JS_SetPropertyStr(ctx, env, "validator_total_stake", JS_NewCFunction(ctx, near_validator_total_stake, "validator_total_stake", 1)); JS_SetPropertyStr(ctx, env, "alt_bn128_g1_multiexp", JS_NewCFunction(ctx, near_alt_bn128_g1_multiexp, "alt_bn128_g1_multiexp", 2)); JS_SetPropertyStr(ctx, env, "alt_bn128_g1_sum", JS_NewCFunction(ctx, near_alt_bn128_g1_sum, "alt_bn128_g1_sum", 2)); JS_SetPropertyStr(ctx, env, "alt_bn128_pairing_check", JS_NewCFunction(ctx, near_alt_bn128_pairing_check, "alt_bn128_pairing_check", 1)); JS_SetPropertyStr(ctx, env, "latin1_string_to_uint8array", JS_NewCFunction(ctx, near_latin1_string_to_uint8array, "latin1_string_to_uint8array", 1)); JS_SetPropertyStr(ctx, env, "utf8_string_to_uint8array", JS_NewCFunction(ctx, near_utf8_string_to_uint8array, "utf8_string_to_uint8array", 1)); JS_SetPropertyStr(ctx, env, "uint8array_to_latin1_string", JS_NewCFunction(ctx, near_uint8array_to_latin1_string, "uint8array_to_latin1_string", 1)); JS_SetPropertyStr(ctx, env, "uint8array_to_utf8_string", JS_NewCFunction(ctx, near_uint8array_to_utf8_string, "uint8array_to_utf8_string", 1)); JS_SetPropertyStr(ctx, global_obj, "env", env); } JSValue JS_Call(JSContext *ctx, JSValueConst func_obj, JSValueConst this_obj, int argc, JSValueConst *argv); void _start() {} #include "methods.h" ================================================ FILE: packages/near-sdk-js/lib/api.d.ts ================================================ import { NearAmount, PromiseIndex } from "./utils"; import { GasWeight } from "./types"; /** * Logs parameters in the NEAR WASM virtual machine. * * @param params - Parameters to log. */ export declare function log(...params: unknown[]): void; /** * Returns the account ID of the account that signed the transaction. * Can only be called in a call or initialize function. */ export declare function signerAccountId(): string; /** * Returns the public key of the account that signed the transaction. * Can only be called in a call or initialize function. */ export declare function signerAccountPk(): Uint8Array; /** * Returns the account ID of the account that called the function. * Can only be called in a call or initialize function. */ export declare function predecessorAccountId(): string; /** * Returns the account ID of the current contract - the contract that is being executed. */ export declare function currentAccountId(): string; /** * Returns the current block index. */ export declare function blockIndex(): bigint; /** * Returns the current block height. */ export declare function blockHeight(): bigint; /** * Returns the current block timestamp. */ export declare function blockTimestamp(): bigint; /** * Returns the current epoch height. */ export declare function epochHeight(): bigint; /** * Returns the amount of NEAR attached to this function call. * Can only be called in payable functions. */ export declare function attachedDeposit(): bigint; /** * Returns the amount of Gas that was attached to this function call. */ export declare function prepaidGas(): bigint; /** * Returns the amount of Gas that has been used by this function call until now. */ export declare function usedGas(): bigint; /** * Returns the current account's account balance. */ export declare function accountBalance(): bigint; /** * Returns the current account's locked balance. */ export declare function accountLockedBalance(): bigint; /** * Reads the value from NEAR storage that is stored under the provided key. * * @param key - The key to read from storage. */ export declare function storageReadRaw(key: Uint8Array): Uint8Array | null; /** * Reads the utf-8 string value from NEAR storage that is stored under the provided key. * * @param key - The utf-8 string key to read from storage. */ export declare function storageRead(key: string): string | null; /** * Checks for the existence of a value under the provided key in NEAR storage. * * @param key - The key to check for in storage. */ export declare function storageHasKeyRaw(key: Uint8Array): boolean; /** * Checks for the existence of a value under the provided utf-8 string key in NEAR storage. * * @param key - The utf-8 string key to check for in storage. */ export declare function storageHasKey(key: string): boolean; /** * Get the last written or removed value from NEAR storage. */ export declare function storageGetEvictedRaw(): Uint8Array; /** * Get the last written or removed value from NEAR storage as utf-8 string. */ export declare function storageGetEvicted(): string; /** * Returns the current accounts NEAR storage usage. */ export declare function storageUsage(): bigint; /** * Writes the provided bytes to NEAR storage under the provided key. * * @param key - The key under which to store the value. * @param value - The value to store. */ export declare function storageWriteRaw(key: Uint8Array, value: Uint8Array): boolean; /** * Writes the provided utf-8 string to NEAR storage under the provided key. * * @param key - The utf-8 string key under which to store the value. * @param value - The utf-8 string value to store. */ export declare function storageWrite(key: string, value: string): boolean; /** * Removes the value of the provided key from NEAR storage. * * @param key - The key to be removed. */ export declare function storageRemoveRaw(key: Uint8Array): boolean; /** * Removes the value of the provided utf-8 string key from NEAR storage. * * @param key - The utf-8 string key to be removed. */ export declare function storageRemove(key: string): boolean; /** * Returns the cost of storing 0 Byte on NEAR storage. */ export declare function storageByteCost(): bigint; /** * Returns the arguments passed to the current smart contract call. */ export declare function inputRaw(): Uint8Array; /** * Returns the arguments passed to the current smart contract call as utf-8 string. */ export declare function input(): string; /** * Returns the value from the NEAR WASM virtual machine. * * @param value - The value to return. */ export declare function valueReturnRaw(value: Uint8Array): void; /** * Returns the utf-8 string value from the NEAR WASM virtual machine. * * @param value - The utf-8 string value to return. */ export declare function valueReturn(value: string): void; /** * Returns a random string of bytes. */ export declare function randomSeed(): Uint8Array; /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export declare function promiseCreateRaw(accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount): PromiseIndex; /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export declare function promiseCreate(accountId: string, methodName: string, args: string, amount: NearAmount, gas: NearAmount): PromiseIndex; /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export declare function promiseThenRaw(promiseIndex: PromiseIndex, accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount): PromiseIndex; /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export declare function promiseThen(promiseIndex: PromiseIndex, accountId: string, methodName: string, args: string, amount: NearAmount, gas: NearAmount): PromiseIndex; /** * Join an arbitrary array of NEAR promises. * * @param promiseIndexes - An arbitrary array of NEAR promise indexes to join. */ export declare function promiseAnd(...promiseIndexes: PromiseIndex[]): PromiseIndex; /** * Create a NEAR promise which will have multiple promise actions inside. * * @param accountId - The account ID of the target contract. */ export declare function promiseBatchCreate(accountId: string): PromiseIndex; /** * Attach a callback NEAR promise to a batch of NEAR promise actions. * * @param promiseIndex - The NEAR promise index of the batch. * @param accountId - The account ID of the target contract. */ export declare function promiseBatchThen(promiseIndex: PromiseIndex, accountId: string): PromiseIndex; /** * Attach a create account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a create account action to. */ export declare function promiseBatchActionCreateAccount(promiseIndex: PromiseIndex): void; /** * Attach a deploy contract promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a deploy contract action to. * @param code - The WASM byte code of the contract to be deployed. */ export declare function promiseBatchActionDeployContract(promiseIndex: PromiseIndex, code: Uint8Array): void; /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export declare function promiseBatchActionFunctionCallRaw(promiseIndex: PromiseIndex, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount): void; /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export declare function promiseBatchActionFunctionCall(promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount): void; /** * Attach a transfer promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a transfer action to. * @param amount - The amount of NEAR to transfer. */ export declare function promiseBatchActionTransfer(promiseIndex: PromiseIndex, amount: NearAmount): void; /** * Attach a stake promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a stake action to. * @param amount - The amount of NEAR to stake. * @param publicKey - The public key with which to stake. */ export declare function promiseBatchActionStake(promiseIndex: PromiseIndex, amount: NearAmount, publicKey: Uint8Array): void; /** * Attach a add full access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add full access key action to. * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ export declare function promiseBatchActionAddKeyWithFullAccess(promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint): void; /** * Attach a add access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add access key action to. * @param publicKey - The public key to add. * @param nonce - The nonce to use. * @param allowance - The allowance of the access key. * @param receiverId - The account ID of the receiver. * @param methodNames - The names of the method to allow the key for. */ export declare function promiseBatchActionAddKeyWithFunctionCall(promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint, allowance: NearAmount, receiverId: string, methodNames: string): void; /** * Attach a delete key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete key action to. * @param publicKey - The public key to delete. */ export declare function promiseBatchActionDeleteKey(promiseIndex: PromiseIndex, publicKey: Uint8Array): void; /** * Attach a delete account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete account action to. * @param beneficiaryId - The account ID of the beneficiary - the account that receives the remaining amount of NEAR. */ export declare function promiseBatchActionDeleteAccount(promiseIndex: PromiseIndex, beneficiaryId: string): void; /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export declare function promiseBatchActionFunctionCallWeightRaw(promiseIndex: PromiseIndex, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount, weight: GasWeight): void; /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export declare function promiseBatchActionFunctionCallWeight(promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount, weight: GasWeight): void; /** * The number of promise results available. */ export declare function promiseResultsCount(): bigint; /** * Returns the result of the NEAR promise for the passed promise index. * * @param promiseIndex - The index of the promise to return the result for. */ export declare function promiseResultRaw(promiseIndex: PromiseIndex): Uint8Array; /** * Returns the result of the NEAR promise for the passed promise index as utf-8 string * * @param promiseIndex - The index of the promise to return the result for. */ export declare function promiseResult(promiseIndex: PromiseIndex): string; /** * Executes the promise in the NEAR WASM virtual machine. * * @param promiseIndex - The index of the promise to execute. */ export declare function promiseReturn(promiseIndex: PromiseIndex): void; /** * Returns sha256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export declare function sha256(value: Uint8Array): Uint8Array; /** * Returns keccak256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export declare function keccak256(value: Uint8Array): Uint8Array; /** * Returns keccak512 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export declare function keccak512(value: Uint8Array): Uint8Array; /** * Returns ripemd160 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export declare function ripemd160(value: Uint8Array): Uint8Array; /** * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding * signature along with v recovery byte. Takes in an additional flag to check for * malleability of the signature which is generally only ideal for transactions. * * @param hash - 32-byte message hash * @param sig - signature * @param v - number of recovery byte * @param malleabilityFlag - whether to check malleability * @returns 64 bytes representing the public key if the recovery was successful. */ export declare function ecrecover(hash: Uint8Array, sig: Uint8Array, v: number, malleabilityFlag: number): Uint8Array | null; /** * Panic the transaction execution with given message * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence */ export declare function panicUtf8(msg: Uint8Array): never; /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-8 sequence */ export declare function logUtf8(msg: Uint8Array): void; /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-16 sequence */ export declare function logUtf16(msg: Uint8Array): void; /** * Returns the number of staked NEAR of given validator, in yoctoNEAR * @param accountId - validator's AccountID * @returns - staked amount */ export declare function validatorStake(accountId: string): bigint; /** * Returns the number of staked NEAR of all validators, in yoctoNEAR * @returns total staked amount */ export declare function validatorTotalStake(): bigint; /** * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i * mul_i g_{1 i} should be equal result. * * @param value - sequence of (g1:G1, fr:Fr), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * `value` is encoded as packed, little-endian * `[((u256, u256), u256)]` slice. * * @returns multi exp sum */ export declare function altBn128G1Multiexp(value: Uint8Array): Uint8Array; /** * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i * (-1)^{sign_i} g_{1 i} should be equal result. * * @param value - sequence of (sign:bool, g1:G1), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns sum over Fq. */ export declare function altBn128G1Sum(value: Uint8Array): Uint8Array; /** * Computes pairing check on alt_bn128 curve. * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing * * @param value - sequence of (g1:G1, g2:G2), where * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 * Fq2 is complex field element (re: Fq, im: Fq) * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq * `value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns whether pairing check pass */ export declare function altBn128PairingCheck(value: Uint8Array): boolean; ================================================ FILE: packages/near-sdk-js/lib/api.js ================================================ import { assert, str, encode, decode, } from "./utils"; import { PromiseResult } from "./types"; const U64_MAX = 2n ** 64n - 1n; const EVICTED_REGISTER = U64_MAX - 1n; /** * Logs parameters in the NEAR WASM virtual machine. * * @param params - Parameters to log. */ export function log(...params) { env.log(params.reduce((accumulated, parameter, index) => { // Stringify undefined const param = parameter === undefined ? "undefined" : parameter; // Convert Objects to strings and convert to string const stringified = typeof param === "object" ? JSON.stringify(param) : `${param}`; if (index === 0) { return stringified; } return `${accumulated} ${stringified}`; }, "")); } /** * Returns the account ID of the account that signed the transaction. * Can only be called in a call or initialize function. */ export function signerAccountId() { env.signer_account_id(0); return str(env.read_register(0)); } /** * Returns the public key of the account that signed the transaction. * Can only be called in a call or initialize function. */ export function signerAccountPk() { env.signer_account_pk(0); return env.read_register(0); } /** * Returns the account ID of the account that called the function. * Can only be called in a call or initialize function. */ export function predecessorAccountId() { env.predecessor_account_id(0); return str(env.read_register(0)); } /** * Returns the account ID of the current contract - the contract that is being executed. */ export function currentAccountId() { env.current_account_id(0); return str(env.read_register(0)); } /** * Returns the current block index. */ export function blockIndex() { return env.block_index(); } /** * Returns the current block height. */ export function blockHeight() { return blockIndex(); } /** * Returns the current block timestamp. */ export function blockTimestamp() { return env.block_timestamp(); } /** * Returns the current epoch height. */ export function epochHeight() { return env.epoch_height(); } /** * Returns the amount of NEAR attached to this function call. * Can only be called in payable functions. */ export function attachedDeposit() { return env.attached_deposit(); } /** * Returns the amount of Gas that was attached to this function call. */ export function prepaidGas() { return env.prepaid_gas(); } /** * Returns the amount of Gas that has been used by this function call until now. */ export function usedGas() { return env.used_gas(); } /** * Returns the current account's account balance. */ export function accountBalance() { return env.account_balance(); } /** * Returns the current account's locked balance. */ export function accountLockedBalance() { return env.account_locked_balance(); } /** * Reads the value from NEAR storage that is stored under the provided key. * * @param key - The key to read from storage. */ export function storageReadRaw(key) { const returnValue = env.storage_read(key, 0); if (returnValue !== 1n) { return null; } return env.read_register(0); } /** * Reads the utf-8 string value from NEAR storage that is stored under the provided key. * * @param key - The utf-8 string key to read from storage. */ export function storageRead(key) { const ret = storageReadRaw(encode(key)); if (ret !== null) { return decode(ret); } return null; } /** * Checks for the existence of a value under the provided key in NEAR storage. * * @param key - The key to check for in storage. */ export function storageHasKeyRaw(key) { return env.storage_has_key(key) === 1n; } /** * Checks for the existence of a value under the provided utf-8 string key in NEAR storage. * * @param key - The utf-8 string key to check for in storage. */ export function storageHasKey(key) { return storageHasKeyRaw(encode(key)); } /** * Get the last written or removed value from NEAR storage. */ export function storageGetEvictedRaw() { return env.read_register(EVICTED_REGISTER); } /** * Get the last written or removed value from NEAR storage as utf-8 string. */ export function storageGetEvicted() { return decode(storageGetEvictedRaw()); } /** * Returns the current accounts NEAR storage usage. */ export function storageUsage() { return env.storage_usage(); } /** * Writes the provided bytes to NEAR storage under the provided key. * * @param key - The key under which to store the value. * @param value - The value to store. */ export function storageWriteRaw(key, value) { return env.storage_write(key, value, EVICTED_REGISTER) === 1n; } /** * Writes the provided utf-8 string to NEAR storage under the provided key. * * @param key - The utf-8 string key under which to store the value. * @param value - The utf-8 string value to store. */ export function storageWrite(key, value) { return storageWriteRaw(encode(key), encode(value)); } /** * Removes the value of the provided key from NEAR storage. * * @param key - The key to be removed. */ export function storageRemoveRaw(key) { return env.storage_remove(key, EVICTED_REGISTER) === 1n; } /** * Removes the value of the provided utf-8 string key from NEAR storage. * * @param key - The utf-8 string key to be removed. */ export function storageRemove(key) { return storageRemoveRaw(encode(key)); } /** * Returns the cost of storing 0 Byte on NEAR storage. */ export function storageByteCost() { return 10000000000000000000n; } /** * Returns the arguments passed to the current smart contract call. */ export function inputRaw() { env.input(0); return env.read_register(0); } /** * Returns the arguments passed to the current smart contract call as utf-8 string. */ export function input() { return decode(inputRaw()); } /** * Returns the value from the NEAR WASM virtual machine. * * @param value - The value to return. */ export function valueReturnRaw(value) { env.value_return(value); } /** * Returns the utf-8 string value from the NEAR WASM virtual machine. * * @param value - The utf-8 string value to return. */ export function valueReturn(value) { valueReturnRaw(encode(value)); } /** * Returns a random string of bytes. */ export function randomSeed() { env.random_seed(0); return env.read_register(0); } /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export function promiseCreateRaw(accountId, methodName, args, amount, gas) { return env.promise_create(accountId, methodName, args, amount, gas); } /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export function promiseCreate(accountId, methodName, args, amount, gas) { return promiseCreateRaw(accountId, methodName, encode(args), amount, gas); } /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseThenRaw(promiseIndex, accountId, methodName, args, amount, gas) { return env.promise_then(promiseIndex, accountId, methodName, args, amount, gas); } /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseThen(promiseIndex, accountId, methodName, args, amount, gas) { return promiseThenRaw(promiseIndex, accountId, methodName, encode(args), amount, gas); } /** * Join an arbitrary array of NEAR promises. * * @param promiseIndexes - An arbitrary array of NEAR promise indexes to join. */ export function promiseAnd(...promiseIndexes) { return env.promise_and(...promiseIndexes); } /** * Create a NEAR promise which will have multiple promise actions inside. * * @param accountId - The account ID of the target contract. */ export function promiseBatchCreate(accountId) { return env.promise_batch_create(accountId); } /** * Attach a callback NEAR promise to a batch of NEAR promise actions. * * @param promiseIndex - The NEAR promise index of the batch. * @param accountId - The account ID of the target contract. */ export function promiseBatchThen(promiseIndex, accountId) { return env.promise_batch_then(promiseIndex, accountId); } /** * Attach a create account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a create account action to. */ export function promiseBatchActionCreateAccount(promiseIndex) { env.promise_batch_action_create_account(promiseIndex); } /** * Attach a deploy contract promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a deploy contract action to. * @param code - The WASM byte code of the contract to be deployed. */ export function promiseBatchActionDeployContract(promiseIndex, code) { env.promise_batch_action_deploy_contract(promiseIndex, code); } /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseBatchActionFunctionCallRaw(promiseIndex, methodName, args, amount, gas) { env.promise_batch_action_function_call(promiseIndex, methodName, args, amount, gas); } /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseBatchActionFunctionCall(promiseIndex, methodName, args, amount, gas) { promiseBatchActionFunctionCallRaw(promiseIndex, methodName, encode(args), amount, gas); } /** * Attach a transfer promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a transfer action to. * @param amount - The amount of NEAR to transfer. */ export function promiseBatchActionTransfer(promiseIndex, amount) { env.promise_batch_action_transfer(promiseIndex, amount); } /** * Attach a stake promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a stake action to. * @param amount - The amount of NEAR to stake. * @param publicKey - The public key with which to stake. */ export function promiseBatchActionStake(promiseIndex, amount, publicKey) { env.promise_batch_action_stake(promiseIndex, amount, publicKey); } /** * Attach a add full access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add full access key action to. * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ export function promiseBatchActionAddKeyWithFullAccess(promiseIndex, publicKey, nonce) { env.promise_batch_action_add_key_with_full_access(promiseIndex, publicKey, nonce); } /** * Attach a add access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add access key action to. * @param publicKey - The public key to add. * @param nonce - The nonce to use. * @param allowance - The allowance of the access key. * @param receiverId - The account ID of the receiver. * @param methodNames - The names of the method to allow the key for. */ export function promiseBatchActionAddKeyWithFunctionCall(promiseIndex, publicKey, nonce, allowance, receiverId, methodNames) { env.promise_batch_action_add_key_with_function_call(promiseIndex, publicKey, nonce, allowance, receiverId, methodNames); } /** * Attach a delete key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete key action to. * @param publicKey - The public key to delete. */ export function promiseBatchActionDeleteKey(promiseIndex, publicKey) { env.promise_batch_action_delete_key(promiseIndex, publicKey); } /** * Attach a delete account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete account action to. * @param beneficiaryId - The account ID of the beneficiary - the account that receives the remaining amount of NEAR. */ export function promiseBatchActionDeleteAccount(promiseIndex, beneficiaryId) { env.promise_batch_action_delete_account(promiseIndex, beneficiaryId); } /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export function promiseBatchActionFunctionCallWeightRaw(promiseIndex, methodName, args, amount, gas, weight) { env.promise_batch_action_function_call_weight(promiseIndex, methodName, args, amount, gas, weight); } /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export function promiseBatchActionFunctionCallWeight(promiseIndex, methodName, args, amount, gas, weight) { promiseBatchActionFunctionCallWeightRaw(promiseIndex, methodName, encode(args), amount, gas, weight); } /** * The number of promise results available. */ export function promiseResultsCount() { return env.promise_results_count(); } /** * Returns the result of the NEAR promise for the passed promise index. * * @param promiseIndex - The index of the promise to return the result for. */ export function promiseResultRaw(promiseIndex) { const status = env.promise_result(promiseIndex, 0); assert(Number(status) === PromiseResult.Successful, `Promise result ${status == PromiseResult.Failed ? "Failed" : status == PromiseResult.NotReady ? "NotReady" : status}`); return env.read_register(0); } /** * Returns the result of the NEAR promise for the passed promise index as utf-8 string * * @param promiseIndex - The index of the promise to return the result for. */ export function promiseResult(promiseIndex) { return decode(promiseResultRaw(promiseIndex)); } /** * Executes the promise in the NEAR WASM virtual machine. * * @param promiseIndex - The index of the promise to execute. */ export function promiseReturn(promiseIndex) { env.promise_return(promiseIndex); } /** * Returns sha256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function sha256(value) { env.sha256(value, 0); return env.read_register(0); } /** * Returns keccak256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function keccak256(value) { env.keccak256(value, 0); return env.read_register(0); } /** * Returns keccak512 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function keccak512(value) { env.keccak512(value, 0); return env.read_register(0); } /** * Returns ripemd160 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function ripemd160(value) { env.ripemd160(value, 0); return env.read_register(0); } /** * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding * signature along with v recovery byte. Takes in an additional flag to check for * malleability of the signature which is generally only ideal for transactions. * * @param hash - 32-byte message hash * @param sig - signature * @param v - number of recovery byte * @param malleabilityFlag - whether to check malleability * @returns 64 bytes representing the public key if the recovery was successful. */ export function ecrecover(hash, sig, v, malleabilityFlag) { const returnValue = env.ecrecover(hash, sig, v, malleabilityFlag, 0); if (returnValue === 0n) { return null; } return env.read_register(0); } // NOTE: "env.panic(msg)" is not exported, use "throw Error(msg)" instead /** * Panic the transaction execution with given message * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence */ export function panicUtf8(msg) { env.panic_utf8(msg); } /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-8 sequence */ export function logUtf8(msg) { env.log_utf8(msg); } /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-16 sequence */ export function logUtf16(msg) { env.log_utf16(msg); } /** * Returns the number of staked NEAR of given validator, in yoctoNEAR * @param accountId - validator's AccountID * @returns - staked amount */ export function validatorStake(accountId) { return env.validator_stake(accountId); } /** * Returns the number of staked NEAR of all validators, in yoctoNEAR * @returns total staked amount */ export function validatorTotalStake() { return env.validator_total_stake(); } /** * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i * mul_i g_{1 i} should be equal result. * * @param value - sequence of (g1:G1, fr:Fr), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * `value` is encoded as packed, little-endian * `[((u256, u256), u256)]` slice. * * @returns multi exp sum */ export function altBn128G1Multiexp(value) { env.alt_bn128_g1_multiexp(value, 0); return env.read_register(0); } /** * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i * (-1)^{sign_i} g_{1 i} should be equal result. * * @param value - sequence of (sign:bool, g1:G1), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns sum over Fq. */ export function altBn128G1Sum(value) { env.alt_bn128_g1_sum(value, 0); return env.read_register(0); } /** * Computes pairing check on alt_bn128 curve. * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing * * @param value - sequence of (g1:G1, g2:G2), where * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 * Fq2 is complex field element (re: Fq, im: Fq) * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq * `value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns whether pairing check pass */ export function altBn128PairingCheck(value) { return env.alt_bn128_pairing_check(value) === 1n; } ================================================ FILE: packages/near-sdk-js/lib/cli/abi.d.ts ================================================ import * as abi from "near-abi"; export declare function runAbiCompilerPlugin(tsFile: string, packageJsonPath: string, tsConfigJsonPath: string): abi.AbiRoot; ================================================ FILE: packages/near-sdk-js/lib/cli/abi.js ================================================ import ts from "typescript"; import JSON5 from 'json5'; import * as abi from "near-abi"; import * as TJS from "near-typescript-json-schema"; import * as fs from "fs"; import { LIB_VERSION } from "../version.js"; function parseMetadata(packageJsonPath) { const packageJson = JSON5.parse(fs.readFileSync(packageJsonPath, "utf8")); let authors = []; if (packageJson["author"]) authors.push(packageJson["author"]); authors = authors.concat(packageJson["contributors"] || []); return { name: packageJson["name"], version: packageJson["version"], authors, build: { compiler: "tsc " + ts.version, builder: "near-sdk-js " + LIB_VERSION, }, }; } function getProgramFromFiles(files, jsonCompilerOptions, basePath = "./") { const { options, errors } = ts.convertCompilerOptionsFromJson(jsonCompilerOptions, basePath); if (errors.length > 0) { errors.forEach((error) => { console.log(error.messageText); }); throw Error("Invalid compiler options"); } return ts.createProgram(files, options); } function validateNearClass(node) { if (node.kind !== ts.SyntaxKind.ClassDeclaration) { throw Error("Expected NEAR function to be inside of a class"); } const classDeclaration = node; const decorators = classDeclaration.decorators || []; const containsNearBindgen = decorators.some((decorator) => { if (decorator.expression.kind !== ts.SyntaxKind.CallExpression) return false; const decoratorExpression = decorator.expression; if (decoratorExpression.expression.kind !== ts.SyntaxKind.Identifier) return false; const decoratorIdentifier = decoratorExpression.expression; const decoratorName = decoratorIdentifier.text; return decoratorName === "NearBindgen"; }); if (!containsNearBindgen) { throw Error("Expected NEAR function to be inside of a class decorated with @NearBindgen"); } } export function runAbiCompilerPlugin(tsFile, packageJsonPath, tsConfigJsonPath) { const tsConfig = JSON5.parse(fs.readFileSync(tsConfigJsonPath, "utf8")); const program = getProgramFromFiles([tsFile], tsConfig["compilerOptions"]); const typeChecker = program.getTypeChecker(); const diagnostics = ts.getPreEmitDiagnostics(program); if (diagnostics.length > 0) { diagnostics.forEach((diagnostic) => { const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); if (diagnostic.file && diagnostic.start) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); console.error(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); } else { console.error(message); } }); throw Error("Failed to compile the contract"); } const generator = TJS.buildGenerator(program); if (!generator) { throw Error("Failed to generate ABI due to an unexpected typescript-json-schema error. Please report this."); } const abiFunctions = []; program.getSourceFiles().forEach((sourceFile, _sourceFileIdx) => { function inspect(node, tc) { if (node.kind === ts.SyntaxKind.MethodDeclaration) { const methodDeclaration = node; const decorators = methodDeclaration.decorators || []; let isCall = false; let isView = false; let isInit = false; const abiModifiers = []; decorators.forEach((decorator) => { if (decorator.expression.kind !== ts.SyntaxKind.CallExpression) return; const decoratorExpression = decorator.expression; if (decoratorExpression.expression.kind !== ts.SyntaxKind.Identifier) return; const decoratorIdentifier = decoratorExpression.expression; const decoratorName = decoratorIdentifier.text; if (decoratorName === "call") { isCall = true; decoratorExpression.arguments.forEach((arg) => { if (arg.kind !== ts.SyntaxKind.ObjectLiteralExpression) return; const objLiteral = arg; objLiteral.properties.forEach((prop) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const propName = prop.name.text; if (propName === "privateFunction") { if (prop.kind !== ts.SyntaxKind.PropertyAssignment) return; const propAssignment = prop; const init = propAssignment.initializer; if (init.kind === ts.SyntaxKind.TrueKeyword) { abiModifiers.push(abi.AbiFunctionModifier.Private); } else if (init.kind === ts.SyntaxKind.FalseKeyword) { // Do nothing } else { throw Error("Unexpected initializer for `privateFunction`: kind " + init.kind); } } if (propName === "payableFunction") { if (prop.kind !== ts.SyntaxKind.PropertyAssignment) return; const propAssignment = prop; const init = propAssignment.initializer; if (init.kind === ts.SyntaxKind.TrueKeyword) { abiModifiers.push(abi.AbiFunctionModifier.Payable); } else if (init.kind === ts.SyntaxKind.FalseKeyword) { // Do nothing } else { throw Error("Unexpected initializer for `publicFunction`: kind " + init.kind); } } }); }); } if (decoratorName === "view") isView = true; if (decoratorName === "initialize") { isInit = true; abiModifiers.push(abi.AbiFunctionModifier.Init); } }); const nearDecoratorsCount = [isCall, isView, isInit].filter((b) => b).length; if (nearDecoratorsCount > 1) { throw Error("NEAR function cannot be init, call and view at the same time"); } if (nearDecoratorsCount === 0) { return; } validateNearClass(node.parent); let abiParams = []; if (methodDeclaration.parameters.length > 1) { throw Error("Expected NEAR function to have a single object parameter, but got " + methodDeclaration.parameters.length); } else if (methodDeclaration.parameters.length === 1) { const jsonObjectParameter = methodDeclaration.parameters[0]; if (!jsonObjectParameter.type) { throw Error("Expected NEAR function to have explicit types, e.g. `{ id }: {id : string }`"); } if (jsonObjectParameter.type.kind !== ts.SyntaxKind.TypeLiteral) { throw Error("Expected NEAR function to have a single object binding parameter, e.g. `{ id }: { id: string }`"); } const typeLiteral = jsonObjectParameter.type; abiParams = typeLiteral.members.map((member) => { if (member.kind !== ts.SyntaxKind.PropertySignature) { throw Error("Expected NEAR function to have a single object binding parameter, e.g. `{ id }: { id: string }`"); } const propertySignature = member; const nodeType = tc.getTypeAtLocation(propertySignature.type); const schema = generator.getTypeDefinition(nodeType, true); const abiParameter = { // eslint-disable-next-line @typescript-eslint/no-explicit-any name: propertySignature.name.text, type_schema: schema, }; return abiParameter; }); } let abiResult = undefined; const returnType = methodDeclaration.type; if (returnType) { const nodeType = tc.getTypeAtLocation(returnType); const schema = generator.getTypeDefinition(nodeType, true); abiResult = { serialization_type: abi.AbiSerializationType.Json, type_schema: schema, }; } const abiFunction = { // eslint-disable-next-line @typescript-eslint/no-explicit-any name: methodDeclaration.name.text, kind: isView ? abi.AbiFunctionKind.View : abi.AbiFunctionKind.Call, }; if (abiModifiers.length > 0) { abiFunction.modifiers = abiModifiers; } if (abiParams.length > 0) { abiFunction.params = { serialization_type: abi.AbiSerializationType.Json, args: abiParams, }; } if (abiResult) { abiFunction.result = abiResult; } abiFunctions.push(abiFunction); } else { ts.forEachChild(node, (n) => inspect(n, tc)); } } inspect(sourceFile, typeChecker); }); const abiRoot = { schema_version: abi.SCHEMA_VERSION, metadata: parseMetadata(packageJsonPath), body: { functions: abiFunctions, root_schema: generator.getSchemaForSymbol("String", true, false), }, }; return abiRoot; } ================================================ FILE: packages/near-sdk-js/lib/cli/build-tools/include-bytes.d.ts ================================================ import { Visitor } from "@babel/traverse"; export default function (): { visitor: Visitor; }; ================================================ FILE: packages/near-sdk-js/lib/cli/build-tools/include-bytes.js ================================================ import * as t from "@babel/types"; import { readFileSync } from "fs"; import { join, dirname } from "path"; const assertStringLiteral = t.assertStringLiteral; export default function () { return { visitor: { CallExpression(path, { opts, file }) { if (!("name" in path.node.callee)) { return; } // Extract the called method name. const name = path.node.callee.name; // If the method name is not "includeBytes" do nothing. if (name === "includeBytes") { // Extract the called method arguments. const args = path.node.arguments; // Get the path of file const filename = file.opts.filename; // User settings const root = opts.root || dirname(filename); // Read binary file into bytes, so encoding is 'latin1' (each byte is 0-255, become one character) const encoding = "latin1"; const [firstArg] = args; // Require first arg to be a string literal assertStringLiteral(firstArg); // Error if filename is not found if (filename === undefined || filename === "unknown") { throw new Error("`includeBytes` function called outside of file"); } if (!("value" in firstArg && typeof firstArg.value === "string")) { throw new Error(`\`includeBytes\` function called with invalid argument: ${args[0]}`); } // Generate and locate the file const fileRelPath = firstArg.value; // Get literal string value const filePath = join(root, fileRelPath); const fileSrc = readFileSync(filePath, { encoding }).toString(); path.replaceWith(t.callExpression(t.memberExpression(t.identifier("env"), t.identifier("latin1_string_to_uint8array")), [t.stringLiteral(fileSrc)])); } }, }, }; } ================================================ FILE: packages/near-sdk-js/lib/cli/build-tools/near-bindgen-exporter.d.ts ================================================ import { Visitor } from "@babel/traverse"; export default function (): { visitor: Visitor; }; ================================================ FILE: packages/near-sdk-js/lib/cli/build-tools/near-bindgen-exporter.js ================================================ import * as t from "@babel/types"; import signal from "signale"; const { Signale } = signal; /** * A list of supported method types/decorators. */ const methodTypes = ["call", "view", "initialize", "migrate"]; /** * A helper function that inserts a new throw Error statement with * the passed message. * * @param message - The message to throw inside the error */ function throwError(message) { return t.blockStatement([ t.throwStatement(t.newExpression(t.identifier("Error"), [t.stringLiteral(message)])), ]); } /** * A helper function that inserts a new state reading expression. * It reads state into _\_state_ via _\_getState_. * * ```typescript * const _state = Contract._getState(); * ``` * * @param classId - The class ID of the class which we are extending. */ function readState(classId, methodType) { if (methodType === "migrate") { return t.variableDeclaration("const", [ t.variableDeclarator(t.identifier("_state"), t.nullLiteral()), ]); } return t.variableDeclaration("const", [ t.variableDeclarator(t.identifier("_state"), t.callExpression(t.memberExpression(classId, t.identifier("_getState")), [])), ]); } /** * A helper function that inserts a double initialization check. * * ```typescript * if (_state) { * throw new Error('Contract already initialized'); * } * ``` * * @param methodType - The type of the method being called. */ function preventDoubleInit(methodType) { if (methodType !== "initialize") { return t.emptyStatement(); } return t.ifStatement(t.identifier("_state"), throwError("Contract already initialized")); } /** * A helper function that inserts a initialization check. * * ```typescript * if (!_state) { * throw new Error('Contract must be initialized'); * } * ``` * * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. * * @returns {t.EmptyStatement | t.IfStatement} */ function ensureInitBeforeCall(classId, methodType) { if (!["call", "view"].includes(methodType)) { return t.emptyStatement(); } return t.ifStatement(t.logicalExpression("&&", t.unaryExpression("!", t.identifier("_state")), t.callExpression(t.memberExpression(classId, t.identifier("_requireInit")), [])), throwError("Contract must be initialized")); } /** * A helper function that inserts a contract creation expression. * It creates a new instance of the class by calling the _\_create_ method * on the contract class. * * ```typescript * let _contract = Contract._create(); * ``` * * @param classId - The class ID of the class being extended. */ function initializeContractClass(classId) { return t.variableDeclaration("const", [ t.variableDeclarator(t.identifier("_contract"), t.callExpression(t.memberExpression(classId, t.identifier("_create")), [])), ]); } /** * A helper function that inserts a state reconstruction statement. * It calls the _\_reconstruct_ method on the _\_contract_ object. * * ```typescript * if (_state) { * Contract._reconstruct(_contract, _state); * } * ``` * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. */ function reconstructState(classId, methodType) { if (!["call", "view"].includes(methodType)) { return t.emptyStatement(); } return t.ifStatement(t.identifier("_state"), t.blockStatement([ t.expressionStatement(t.callExpression(t.memberExpression(classId, t.identifier("_reconstruct")), [t.identifier("_contract"), t.identifier("_state")])), ])); } /** * A helper function that inserts a argument collection expression. * It calls the _\_getArgs_ function on the class object. * * ```typescript * const _args = Contract._getArgs(); * ``` * @param classId - The class ID of the class being extended. */ function collectArguments(classId) { return t.variableDeclaration("const", [ t.variableDeclarator(t.identifier("_args"), t.callExpression(t.memberExpression(classId, t.identifier("_getArgs")), [])), ]); } /** * A helper function that inserts a contract method call expression. * It calls the appropriate contract method and passes the collected _\_args_. * * ```typescript * const _result = _contract.method(args); * ``` * * @param methodName - The name of the method being called. */ function callContractMethod(methodName) { return t.variableDeclaration("const", [ t.variableDeclarator(t.identifier("_result"), t.callExpression(t.memberExpression(t.identifier("_contract"), t.identifier(methodName)), [t.identifier("_args")])), ]); } /** * A helper function that inserts a save to storage expression. * It calls the _\_saveToStorage_ method if a initialize or call method is called. * * ```typescript * Contract._saveToStorage(_contract); * ``` * * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. */ function saveToStorage(classId, methodType) { if (!["initialize", "call", "migrate"].includes(methodType)) { return t.emptyStatement(); } return t.expressionStatement(t.callExpression(t.memberExpression(classId, t.identifier("_saveToStorage")), [t.identifier("_contract")])); } /** * A helper function that inserts a NearPromise execution call or a valuer return call. * It checks for the return type of the called function and either performs a NearPromise * _onReturn_ call or a _value\_return_ environment function to return the value to the callee. * * ```typescript * if (_result !== undefined) { * if (_result && _result.constructor && _result.constructor.name === 'NearPromise') { * _result.onReturn(); * } else { * near.valueReturnRaw(_contract._serialize(result)); * } * } * ``` * * @param classId - The class ID of the class being extended. */ function executePromise(classId) { return t.ifStatement(t.binaryExpression("!==", t.identifier("_result"), t.identifier("undefined")), t.ifStatement(t.logicalExpression("&&", t.logicalExpression("&&", t.identifier("_result"), t.memberExpression(t.identifier("_result"), t.identifier("constructor"))), t.binaryExpression("===", t.memberExpression(t.memberExpression(t.identifier("_result"), t.identifier("constructor")), t.identifier("name")), t.stringLiteral("NearPromise"))), t.expressionStatement(t.callExpression(t.memberExpression(t.identifier("_result"), t.identifier("onReturn")), [])), t.expressionStatement(t.callExpression(t.memberExpression(t.identifier("env"), t.identifier("value_return")), [ t.callExpression(t.memberExpression(classId, t.identifier("_serialize")), [t.identifier("_result"), t.booleanLiteral(true)]), ])))); } /** * A helper function that inserts the overridden function declaration into the class. * * @param classId - The class ID of the class being extended. * @param methodName - The name of the method being called. * @param methodType - The type of the method being called. */ function createDeclaration(classId, methodName, methodType) { return t.exportNamedDeclaration(t.functionDeclaration(t.identifier(methodName), [], t.blockStatement([ // Read the state of the contract from storage. // const _state = Contract._getState(); readState(classId, methodType), // Throw if initialized on any subsequent init function calls. // if (_state) { throw new Error('Contract already initialized'); } preventDoubleInit(methodType), // Throw if NOT initialized on any non init function calls. // if (!_state) { throw new Error('Contract must be initialized'); } ensureInitBeforeCall(classId, methodType), // Create instance of contract by calling _create function. // let _contract = Contract._create(); initializeContractClass(classId), // Reconstruct the contract with the state if the state is valid. // if (_state) { Contract._reconstruct(_contract, _state); } reconstructState(classId, methodType), // Collect the arguments sent to the function. // const _args = Contract._getArgs(); collectArguments(classId), // Perform the actual function call to the appropriate contract method. // const _result = _contract.method(args); callContractMethod(methodName), // If the method called is either an initialize or call method type, save the changes to storage. // Contract._saveToStorage(_contract); saveToStorage(classId, methodType), // If a NearPromise is returned from the function call the onReturn method to execute the promise. // if (_result !== undefined) // if (_result && _result.constructor && _result.constructor.name === 'NearPromise') // _result.onReturn(); // else // near.valueReturnRaw(_contract._serialize(result)); executePromise(classId), ]))); } export default function () { return { visitor: { ClassDeclaration(path, { opts: { verbose } }) { // Capture the node of the current path. const classNode = path.node; // Check that the class is decorated with NearBindgen otherwise do nothing. if (classNode.decorators && "callee" in classNode.decorators[0].expression && "name" in classNode.decorators[0].expression.callee && classNode.decorators[0].expression.callee.name === "NearBindgen") { // Iterate over the children of the class node. classNode.body.body.forEach((child) => { // Check that the child is a class method and has decorators. if (child.type === "ClassMethod" && child.kind === "method" && child.decorators && "callee" in child.decorators[0].expression && "name" in child.decorators[0].expression.callee) { // Capture the decorator name. const methodType = child.decorators[0].expression.callee.name; // Check that the decorator is one of the supported method types. if (methodTypes.includes(methodType) && "name" in child.key) { // Insert the method override into the class declaration. path.insertAfter(createDeclaration(classNode.id, child.key.name, methodType)); if (verbose) { new Signale({ scope: "near-bindgen-exporter", }).info(`Babel ${child.key.name} method export done.`); } } } }); } }, }, }; } ================================================ FILE: packages/near-sdk-js/lib/cli/cli.d.ts ================================================ #!/usr/bin/env node export declare function validateCom(source: string, { verbose }: { verbose: boolean; }): Promise; export declare function checkTypescriptCom(source: string, { verbose }: { verbose: boolean; }): Promise; export declare function generateAbi(source: string, target: string, packageJson: string, tsConfig: string, { verbose }: { verbose: boolean; }): Promise; export declare function createJsFileWithRollupCom(source: string, target: string, { verbose }: { verbose: boolean; }): Promise; export declare function transpileJsAndBuildWasmCom(target: string, { verbose }: { verbose: boolean; }): Promise; export declare function buildCom(source: string, target: string, packageJson: string, tsConfig: string, { verbose, generateABI }: { verbose: boolean; generateABI: boolean; }): Promise; ================================================ FILE: packages/near-sdk-js/lib/cli/cli.js ================================================ #!/usr/bin/env node import fs from "fs"; import path, { basename, dirname } from "path"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import sourcemaps from "rollup-plugin-sourcemaps"; import { babel } from "@rollup/plugin-babel"; import { rollup } from "rollup"; import { Command } from "commander"; import signal from "signale"; import { executeCommand, validateContract } from "./utils.js"; import { runAbiCompilerPlugin } from "./abi.js"; const { Signale } = signal; const PROJECT_DIR = process.cwd(); const NEAR_SDK_JS = "node_modules/near-sdk-js"; const TSC = "node_modules/.bin/tsc"; const QJSC_DIR = `${NEAR_SDK_JS}/lib/cli/deps/quickjs`; const QJSC = `${NEAR_SDK_JS}/lib/cli/deps/qjsc`; const program = new Command(); program .name("near-sdk-js") .addCommand(new Command("build") .usage("[source] [target]") .description("Build NEAR JS Smart-contract") .argument("[source]", "Contract to build.", "src/index.js") .argument("[target]", "Target file path and name.", "build/contract.wasm") .argument("[packageJson]", "Target file path and name.", "package.json") .argument("[tsConfig]", "Target file path and name.", "tsconfig.json") .option("--verbose", "Whether to print more verbose output.", false) .option("--generateABI", "Whether to generate ABI.", false) .action(buildCom)) .addCommand(new Command("validateContract") .usage("[source]") .description("Validate a NEAR JS Smart-contract. Validates the contract by checking that all parameters are initialized in the constructor. Works only for typescript.") .argument("[source]", "Contract to validate.", "src/index.ts") .option("--verbose", "Whether to print more verbose output.", false) .action(validateCom)) .addCommand(new Command("checkTypescript") .usage("[source]") .description("Run TSC with some cli flags - warning - ignores tsconfig.json.") .argument("[source]", "Typescript file to validate", "src/index.ts") .option("--verbose", "Whether to print more verbose output.", false) .action(checkTypescriptCom)) .addCommand(new Command("createJsFileWithRollup") .usage("[source] [target]") .description("Create intermediate javascript file for later processing with QJSC") .argument("[source]", "Contract to build.", "src/index.js") .argument("[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm") .option("--verbose", "Whether to print more verbose output.", false) .action(createJsFileWithRollupCom)) .addCommand(new Command("transpileJsAndBuildWasm") .usage("[source] [target]") .description("Transpiles the target javascript file into .c and .h using QJSC then compiles that into wasm using clang") .argument("[target]", "Target file path and name. The js file must correspond to the same path with the js extension.", "build/contract.wasm") .option("--verbose", "Whether to print more verbose output.", false) .action(transpileJsAndBuildWasmCom)) .parse(); function getTargetDir(target) { return dirname(target); } function getTargetExt(target) { return target.split(".").pop(); } function getTargetFileName(target) { return basename(target, `.${getTargetExt(target)}`); } function getRollupTarget(target) { return `${getTargetDir(target)}/${getTargetFileName(target)}.js`; } function getQjscTarget(target) { return `${getTargetDir(target)}/${getTargetFileName(target)}.h`; } function getContractTarget(target) { return `${getTargetDir(target)}/${getTargetFileName(target)}.wasm`; } function getContractAbi(target) { return `${getTargetDir(target)}/${getTargetFileName(target)}-abi.json`; } function requireTargetExt(target) { if (getTargetExt(target) === "wasm") { return; } signal.error(`Unsupported target ${getTargetExt(target)}, make sure target ends with .wasm!`); process.exit(1); } function ensureTargetDirExists(target) { const targetDir = getTargetDir(target); if (fs.existsSync(targetDir)) { return; } signal.await(`Creating ${targetDir} directory...`); fs.mkdirSync(targetDir, {}); } export async function validateCom(source, { verbose = false }) { const signale = new Signale({ scope: "validate", interactive: !verbose }); signale.await(`Validating ${source} contract...`); if (!(await validateContract(source, verbose))) { process.exit(1); } } export async function checkTypescriptCom(source, { verbose = false }) { const signale = new Signale({ scope: "checkTypescript", interactive: !verbose, }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { signale.info(`Source file is not a typescript file ${source}`); return; } signale.await(`Typechecking ${source} with tsc...`); await checkTsBuildWithTsc(source, verbose); } export async function generateAbi(source, target, packageJson, tsConfig, { verbose = false }) { const signale = new Signale({ scope: "generateAbi", interactive: !verbose }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { signale.info(`Skipping ABI generation as source file is not a typescript file ${source}`); return; } signale.await("Generating ABI..."); const abi = runAbiCompilerPlugin(source, packageJson, tsConfig); fs.writeFileSync(getContractAbi(target), JSON.stringify(abi, null, 2)); signale.success(`Generated ${getContractAbi(target)} ABI successfully!`); } export async function createJsFileWithRollupCom(source, target, { verbose = false }) { const signale = new Signale({ scope: "createJsFileWithRollup", interactive: !verbose, }); requireTargetExt(target); ensureTargetDirExists(target); signale.await(`Creating ${source} file with Rollup...`); await createJsFileWithRullup(source, getRollupTarget(target), verbose); } export async function transpileJsAndBuildWasmCom(target, { verbose = false }) { const signale = new Signale({ scope: "transpileJsAndBuildWasm", interactive: !verbose, }); requireTargetExt(target); ensureTargetDirExists(target); signale.await(`Creating ${getQjscTarget(target)} file with QJSC...`); await createHeaderFileWithQjsc(getRollupTarget(target), getQjscTarget(target), verbose); signale.await("Generating methods.h file..."); await createMethodsHeaderFile(getRollupTarget(target), verbose); signale.await(`Creating ${getContractTarget(target)} contract...`); await createWasmContract(getQjscTarget(target), getContractTarget(target), verbose); signale.await("Executing wasi-stub..."); await wasiStubContract(getContractTarget(target), verbose); signale.success(`Generated ${getContractTarget(target)} contract successfully!`); } export async function buildCom(source, target, packageJson, tsConfig, { verbose = false, generateABI = false }) { const signale = new Signale({ scope: "build", interactive: !verbose }); requireTargetExt(target); signale.await(`Building ${source} contract...`); await checkTypescriptCom(source, { verbose }); ensureTargetDirExists(target); if (generateABI) { await generateAbi(source, target, packageJson, tsConfig, { verbose }); } await validateCom(source, { verbose }); await createJsFileWithRollupCom(source, target, { verbose }); await transpileJsAndBuildWasmCom(target, { verbose }); } async function checkTsBuildWithTsc(sourceFileWithPath, verbose = false) { await executeCommand(`${TSC} --noEmit --skipLibCheck --experimentalDecorators --target es2020 --moduleResolution node ${sourceFileWithPath}`, verbose); } // Common build function async function createJsFileWithRullup(sourceFileWithPath, rollupTarget, verbose = false) { const bundle = await rollup({ input: sourceFileWithPath, plugins: [ nodeResolve({ extensions: [".js", ".ts"], }), sourcemaps(), // commonjs(), babel({ babelHelpers: "bundled", extensions: [".ts", ".js", ".jsx", ".es6", ".es", ".mjs"], presets: ["@babel/preset-typescript"], plugins: [ "near-sdk-js/lib/cli/build-tools/include-bytes.js", [ "near-sdk-js/lib/cli/build-tools/near-bindgen-exporter.js", { verbose }, ], ["@babel/plugin-proposal-decorators", { version: "legacy" }], ], }), ], }); await bundle.write({ sourcemap: true, file: rollupTarget, format: "es", }); } async function createHeaderFileWithQjsc(rollupTarget, qjscTarget, verbose = false) { await executeCommand(`${QJSC} -c -m -o ${qjscTarget} -N code ${rollupTarget}`, verbose); } async function createMethodsHeaderFile(rollupTarget, verbose = false) { const buildPath = path.dirname(rollupTarget); if (verbose) { new Signale({ scope: "method-header" }).info(rollupTarget); } const mod = await import(`${PROJECT_DIR}/${rollupTarget}`); const exportNames = Object.keys(mod); if (exportNames.includes('panic')) { signal.error("'panic' is a reserved word, please use another name for contract method"); process.exit(1); } const methods = exportNames.reduce((result, key) => `${result}DEFINE_NEAR_METHOD(${key})\n`, ""); fs.writeFileSync(`${buildPath}/methods.h`, methods); } async function createWasmContract(qjscTarget, contractTarget, verbose = false) { const WASI_SDK_PATH = `${NEAR_SDK_JS}/lib/cli/deps/wasi-sdk`; const CC = `${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot`; const DEFS = `-D_GNU_SOURCE '-DCONFIG_VERSION="2021-03-27"' -DCONFIG_BIGNUM`; const INCLUDES = `-I${QJSC_DIR} -I.`; const ORIGINAL_BUILDER_PATH = `${NEAR_SDK_JS}/builder/builder.c`; const NEW_BUILDER_PATH = `${path.dirname(contractTarget)}/builder.c`; const SOURCES = `${NEW_BUILDER_PATH} ${QJSC_DIR}/quickjs.c ${QJSC_DIR}/libregexp.c ${QJSC_DIR}/libunicode.c ${QJSC_DIR}/cutils.c ${QJSC_DIR}/quickjs-libc-min.c ${QJSC_DIR}/libbf.c`; const LIBS = `-lm`; // copying builder.c file to the build folder fs.cpSync(ORIGINAL_BUILDER_PATH, NEW_BUILDER_PATH); fs.renameSync(qjscTarget, "build/code.h"); await executeCommand(`${CC} --target=wasm32-wasi -nostartfiles -Oz -flto ${DEFS} ${INCLUDES} ${SOURCES} ${LIBS} -Wl,--no-entry -Wl,--allow-undefined -Wl,-z,stack-size=${256 * 1024} -Wl,--lto-O3 -o ${contractTarget}`, verbose); } async function wasiStubContract(contractTarget, verbose = false) { const WASI_STUB = `${NEAR_SDK_JS}/lib/cli/deps/binaryen/wasi-stub/run.sh`; await executeCommand(`${WASI_STUB} ${contractTarget}`, verbose); } ================================================ FILE: packages/near-sdk-js/lib/cli/post-install.d.ts ================================================ export {}; ================================================ FILE: packages/near-sdk-js/lib/cli/post-install.js ================================================ import { executeCommand, download } from "./utils.js"; import signal from "signale"; import os from "os"; import fs from "fs"; const { Signale } = signal; const signale = new Signale({ scope: "postinstall", interactive: true }); // Clean existing deps folder process.chdir("lib/cli"); const DEPS = "deps"; fs.rmSync(DEPS, { recursive: true, force: true }); fs.mkdirSync(DEPS); process.chdir(DEPS); const PLATFORM = os.platform(); const ARCH = os.arch(); console.log(`Current platform: ${PLATFORM}, current architecture: ${ARCH}`); const SUPPORTED_PLATFORMS = ["linux", "darwin"]; // Unsupported platforms: 'win32', 'aix', 'freebsd', 'openbsd', 'sunos', 'android' const SUPPORTED_ARCH = ["x64", "arm64"]; // Unsupported arch: 'arm', 'ia32', 'mips','mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x32' if (!SUPPORTED_PLATFORMS.includes(PLATFORM)) { console.error(`Platform ${PLATFORM} is not supported at the moment`); process.exit(1); } if (!SUPPORTED_ARCH.includes(ARCH)) { console.error(`Architecture ${ARCH} is not supported at the moment`); process.exit(1); } signale.await("Installing wasi-stub..."); const BINARYEN_VERSION = `0.1.16`; const BINARYEN_VERSION_TAG = `v${BINARYEN_VERSION}`; const BINARYEN_SYSTEM_NAME = PLATFORM === "linux" ? "Linux" : PLATFORM === "darwin" ? "macOS" : PLATFORM === "win32" ? "windows" : "other"; const BINARYEN_ARCH_NAME = (ARCH == 'aarch64') ? 'ARM64' : ARCH.toUpperCase(); const BINARYEN_TAR_NAME = `binaryen-${BINARYEN_SYSTEM_NAME}-${BINARYEN_ARCH_NAME}.tar.gz`; await download(`https://github.com/ailisp/binaryen/releases/download/${BINARYEN_VERSION_TAG}/${BINARYEN_TAR_NAME}`); fs.mkdirSync("binaryen"); await executeCommand(`tar xvf ${BINARYEN_TAR_NAME} --directory binaryen`); fs.rmSync(BINARYEN_TAR_NAME); signale.await("Installing QuickJS..."); const QUICK_JS_VERSION = `0.1.3`; const QUICK_JS_VERSION_TAG = `v${QUICK_JS_VERSION}`; const QUICK_JS_SYSTEM_NAME = PLATFORM === "linux" ? "Linux" : PLATFORM === "darwin" ? "macOS" : PLATFORM === "win32" ? "windows" : "other"; const QUICK_JS_ARCH_NAME = ARCH === "x64" ? "X64" : ARCH === "arm64" ? "arm64" : "other"; const QUICK_JS_TAR_NAME = `${QUICK_JS_VERSION_TAG}.tar.gz`; const QUICK_JS_DOWNLOADED_FOLDER_NAME = `quickjs-${QUICK_JS_VERSION}`; const QUICK_JS_TARGET_FOLDER_NAME = "quickjs"; const QUICK_JS_DOWNLOADED_NAME = `qjsc-${QUICK_JS_SYSTEM_NAME}-${QUICK_JS_ARCH_NAME}`; const QUICK_JS_TARGET_NAME = "qjsc"; // Download QuickJS await download(`https://github.com/near/quickjs/releases/download/${QUICK_JS_VERSION_TAG}/qjsc-${QUICK_JS_SYSTEM_NAME}-${QUICK_JS_ARCH_NAME}`); await download(`https://github.com/near/quickjs/archive/refs/tags/${QUICK_JS_VERSION_TAG}.tar.gz`); // Extract QuickJS await executeCommand(`tar xvf ${QUICK_JS_TAR_NAME}`); // Delete .tar file fs.rmSync(QUICK_JS_TAR_NAME); // Delete version from folder name fs.renameSync(QUICK_JS_DOWNLOADED_FOLDER_NAME, QUICK_JS_TARGET_FOLDER_NAME); // Rename qjsc file fs.renameSync(QUICK_JS_DOWNLOADED_NAME, QUICK_JS_TARGET_NAME); // chmod qjsc fs.chmodSync(QUICK_JS_TARGET_NAME, 0o755); signale.await("Installing wasi-sdk..."); const WASI_SDK_MAJOR_VER = 11; const WASI_SDK_MINOR_VER = 0; const WASI_SDK_DOWNLOADED_FOLDER_NAME = `wasi-sdk-${WASI_SDK_MAJOR_VER}.${WASI_SDK_MINOR_VER}`; const WASI_SDK_SYSTEM_NAME = PLATFORM === "linux" ? "linux" : PLATFORM === "darwin" ? "macos" : PLATFORM === "win32" ? "windows" : "other"; const WASI_SDK_TAR_NAME = `${WASI_SDK_DOWNLOADED_FOLDER_NAME}-${WASI_SDK_SYSTEM_NAME}.tar.gz`; // Download WASI SDK await download(`https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_MAJOR_VER}/${WASI_SDK_TAR_NAME}`); // Extract WASI SDK await executeCommand(`tar xvf ${WASI_SDK_TAR_NAME}`); // Delete .tar file fs.rmSync(WASI_SDK_TAR_NAME); // Delete version from folder name fs.renameSync(WASI_SDK_DOWNLOADED_FOLDER_NAME, "wasi-sdk"); signale.success("Successfully finished postinstall script!"); ================================================ FILE: packages/near-sdk-js/lib/cli/utils.d.ts ================================================ export declare function executeCommand(command: string, verbose?: boolean): Promise; export declare function download(url: string, verbose?: boolean): Promise; /** * Validates the contract by checking that all parameters are initialized in the constructor. Works only for contracts written in TypeScript. * * @param contractPath - Path to the contract. * @param verbose - Whether to print verbose output. **/ export declare function validateContract(contractPath: string, verbose?: boolean): Promise; ================================================ FILE: packages/near-sdk-js/lib/cli/utils.js ================================================ import childProcess from "child_process"; import { promisify } from "util"; import signal from "signale"; import { Project } from "ts-morph"; import chalk from "chalk"; const { Signale } = signal; const exec = promisify(childProcess.exec); export async function executeCommand(command, verbose = false) { const signale = new Signale({ scope: "exec", interactive: !verbose }); if (verbose) { signale.info(`Running command: ${command}`); } let stdout, stderr, code = 0; try { ({ stdout, stderr } = await exec(command)); } catch (error) { ({ stdout, stderr, code } = error); } if (code != 0) { signale.error(`Command failed: ${command}`); const failDueToNameConflict = stderr.match(/conflicting types for '([a-zA-Z0-9_]+)'/); if (failDueToNameConflict && failDueToNameConflict.length > 1) { signale.error(`'${failDueToNameConflict[1]}' is a reserved word, please use another name for contract method"`); } } if (stderr && verbose) { signale.error(`Command stderr: ${stderr}`); } if (verbose) { signale.info(`Command stdout: ${stdout}`); } if (code != 0) { process.exit(1); } return stdout.trim(); } export async function download(url, verbose = false) { await executeCommand(`curl -LOf ${url}`, verbose); } const UNINITIALIZED_PARAMETERS_ERROR = "All parameters must be initialized in the constructor. Uninitialized parameters:"; /** * Validates the contract by checking that all parameters are initialized in the constructor. Works only for contracts written in TypeScript. * * @param contractPath - Path to the contract. * @param verbose - Whether to print verbose output. **/ export async function validateContract(contractPath, verbose = false) { const signale = new Signale({ scope: "validate-contract" }); const project = new Project(); project.addSourceFilesAtPaths(contractPath); const sourceFile = project.getSourceFile(contractPath); const classDeclarations = sourceFile.getClasses(); for (const classDeclaration of classDeclarations) { const classStructure = classDeclaration.getStructure(); const { decorators, properties, name } = classStructure; const hasNearBindgen = decorators.some(({ name }) => name === "NearBindgen"); if (hasNearBindgen) { if (verbose) { signale.info(`Validating ${name} class...`); } const constructors = classDeclaration.getConstructors(); const hasConstructor = constructors.length > 0; const propertiesToBeInited = properties.filter(({ initializer }) => !initializer); if (!hasConstructor && propertiesToBeInited.length === 0) { return true; } if (!hasConstructor && propertiesToBeInited.length > 0) { signale.error(chalk.redBright(`${UNINITIALIZED_PARAMETERS_ERROR} ${propertiesToBeInited .map(({ name }) => name) .join(", ")}`)); return false; } const [constructor] = constructors; const constructorContent = constructor.getText(); if (verbose) { signale.info("Checking for non initialized properties..."); } const nonInitedProperties = propertiesToBeInited.reduce((properties, { name }) => { if (constructorContent.includes(`this.${name}`)) { return properties; } return [...properties, name]; }, []); if (nonInitedProperties.length > 0) { signale.error(chalk.redBright(`${UNINITIALIZED_PARAMETERS_ERROR} ${nonInitedProperties.join(", ")}`)); return false; } } } return true; } ================================================ FILE: packages/near-sdk-js/lib/collections/index.d.ts ================================================ export * from "./lookup-map"; export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; export * from "./subtype"; ================================================ FILE: packages/near-sdk-js/lib/collections/index.js ================================================ export * from "./lookup-map"; export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; export * from "./subtype"; ================================================ FILE: packages/near-sdk-js/lib/collections/lookup-map.d.ts ================================================ import { GetOptions } from "../types/collections"; import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ export declare class LookupMap extends SubType { readonly keyPrefix: string; /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(keyPrefix: string); /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. */ containsKey(key: string): boolean; /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get(key: string, options?: Omit, "serializer">): DataType | null; /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove(key: string, options?: Omit, "serializer">): DataType | null; /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param newValue - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set(key: string, newValue: DataType, options?: GetOptions): DataType | null; /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. * @param options - Options for storing the data. */ extend(keyValuePairs: [string, DataType][], options?: GetOptions): void; /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array; /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: LookupMap): LookupMap; } ================================================ FILE: packages/near-sdk-js/lib/collections/lookup-map.js ================================================ import * as near from "../api"; import { getValueWithOptions, serializeValueWithOptions, encode, } from "../utils"; import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ export class LookupMap extends SubType { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(keyPrefix) { super(); this.keyPrefix = keyPrefix; } /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. */ containsKey(key) { const storageKey = this.keyPrefix + key; return near.storageHasKey(storageKey); } /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get(key, options) { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); if (options == undefined) { options = {}; } options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove(key, options) { const storageKey = this.keyPrefix + key; if (!near.storageRemove(storageKey)) { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param newValue - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set(key, newValue, options) { const storageKey = this.keyPrefix + key; const storageValue = serializeValueWithOptions(newValue, options); if (!near.storageWriteRaw(encode(storageKey), storageValue)) { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. * @param options - Options for storing the data. */ extend(keyValuePairs, options) { for (const [key, value] of keyValuePairs) { this.set(key, value, options); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options) { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data) { return new LookupMap(data.keyPrefix); } } ================================================ FILE: packages/near-sdk-js/lib/collections/lookup-set.d.ts ================================================ import { GetOptions } from "../types/collections"; /** * A lookup set collection that stores entries in NEAR storage. */ export declare class LookupSet { readonly keyPrefix: string; /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(keyPrefix: string); /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. * @param options - Options for storing data. */ contains(key: DataType, options?: Pick, "serializer">): boolean; /** * Returns true if the element was present in the set. * * @param key - The entry to remove. * @param options - Options for storing data. */ remove(key: DataType, options?: Pick, "serializer">): boolean; /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param key - The value to store in the collection. * @param options - Options for storing the data. */ set(key: DataType, options?: Pick, "serializer">): boolean; /** * Extends the current collection with the passed in array of elements. * * @param keys - The elements to extend the collection with. * @param options - Options for storing the data. */ extend(keys: DataType[], options?: Pick, "serializer">): void; /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array; /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: LookupSet): LookupSet; } ================================================ FILE: packages/near-sdk-js/lib/collections/lookup-set.js ================================================ import * as near from "../api"; import { serializeValueWithOptions } from "../utils"; /** * A lookup set collection that stores entries in NEAR storage. */ export class LookupSet { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(keyPrefix) { this.keyPrefix = keyPrefix; } /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. * @param options - Options for storing data. */ contains(key, options) { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return near.storageHasKey(storageKey); } /** * Returns true if the element was present in the set. * * @param key - The entry to remove. * @param options - Options for storing data. */ remove(key, options) { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return near.storageRemove(storageKey); } /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param key - The value to store in the collection. * @param options - Options for storing the data. */ set(key, options) { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return !near.storageWrite(storageKey, ""); } /** * Extends the current collection with the passed in array of elements. * * @param keys - The elements to extend the collection with. * @param options - Options for storing the data. */ extend(keys, options) { keys.forEach((key) => this.set(key, options)); } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options) { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data) { return new LookupSet(data.keyPrefix); } } ================================================ FILE: packages/near-sdk-js/lib/collections/subtype.d.ts ================================================ import { GetOptions } from "../types/collections"; export declare abstract class SubType { subtype(): any; set_reconstructor(options?: Omit, "serializer">): Omit, "serializer">; } ================================================ FILE: packages/near-sdk-js/lib/collections/subtype.js ================================================ export class SubType { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ subtype() { } set_reconstructor(options) { if (options == undefined) { options = {}; } const subtype = this.subtype(); if (options.reconstructor == undefined && subtype != undefined) { if ( // eslint-disable-next-line no-prototype-builtins subtype.hasOwnProperty("class") && typeof subtype.class.reconstruct === "function") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = subtype.class.reconstruct; } else if (typeof subtype.reconstruct === "function") { options.reconstructor = subtype.reconstruct; } } return options; } } ================================================ FILE: packages/near-sdk-js/lib/collections/unordered-map.d.ts ================================================ import { Vector } from "./vector"; import { LookupMap } from "./lookup-map"; import { GetOptions } from "../types/collections"; import { SubType } from "./subtype"; declare type ValueAndIndex = [value: string, index: number]; /** * An unordered map that stores data in NEAR storage. */ export declare class UnorderedMap extends SubType { readonly prefix: string; readonly _keys: Vector; readonly values: LookupMap; /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(prefix: string); /** * The number of elements stored in the collection. */ get length(): number; /** * Checks whether the collection is empty. */ isEmpty(): boolean; /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get(key: string, options?: Omit, "serializer">): DataType | null; /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param value - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set(key: string, value: DataType, options?: GetOptions): DataType | null; /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove(key: string, options?: Omit, "serializer">): DataType | null; /** * Remove all of the elements stored within the collection. */ clear(): void; [Symbol.iterator](): UnorderedMapIterator; /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions; /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): [string, DataType][]; /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. */ extend(keyValuePairs: [string, DataType][]): void; /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array; /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: UnorderedMap): UnorderedMap; keys({ start, limit }: { start: any; limit: any; }): string[]; } /** * An iterator for the UnorderedMap collection. */ declare class UnorderedMapIterator { private options?; private keys; private map; /** * @param unorderedMap - The unordered map collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor(unorderedMap: UnorderedMap, options?: GetOptions); subtype(): any; next(): { value: [string | null, DataType | null]; done: boolean; }; } export {}; ================================================ FILE: packages/near-sdk-js/lib/collections/unordered-map.js ================================================ import { assert, ERR_INCONSISTENT_STATE, getValueWithOptions, serializeValueWithOptions, encode, decode, } from "../utils"; import { Vector, VectorIterator } from "./vector"; import { LookupMap } from "./lookup-map"; import { SubType } from "./subtype"; /** * An unordered map that stores data in NEAR storage. */ export class UnorderedMap extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(prefix) { super(); this.prefix = prefix; this._keys = new Vector(`${prefix}u`); // intentional different prefix with old UnorderedMap this.values = new LookupMap(`${prefix}m`); } /** * The number of elements stored in the collection. */ get length() { return this._keys.length; } /** * Checks whether the collection is empty. */ isEmpty() { return this._keys.isEmpty(); } /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get(key, options) { const valueAndIndex = this.values.get(key); if (valueAndIndex === null) { return options?.defaultValue ?? null; } options = this.set_reconstructor(options); const [value] = valueAndIndex; return getValueWithOptions(this.subtype(), encode(value), options); } /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param value - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set(key, value, options) { const valueAndIndex = this.values.get(key); const serialized = serializeValueWithOptions(value, options); if (valueAndIndex === null) { const newElementIndex = this.length; this._keys.push(key); this.values.set(key, [decode(serialized), newElementIndex]); return null; } const [oldValue, oldIndex] = valueAndIndex; this.values.set(key, [decode(serialized), oldIndex]); return getValueWithOptions(this.subtype(), encode(oldValue), options); } /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove(key, options) { const oldValueAndIndex = this.values.remove(key); if (oldValueAndIndex === null) { return options?.defaultValue ?? null; } const [value, index] = oldValueAndIndex; assert(this._keys.swapRemove(index) !== null, ERR_INCONSISTENT_STATE); // the last key is swapped to key[index], the corresponding [value, index] need update if (!this._keys.isEmpty() && index !== this._keys.length) { // if there is still elements and it was not the last element const swappedKey = this._keys.get(index); const swappedValueAndIndex = this.values.get(swappedKey); assert(swappedValueAndIndex !== null, ERR_INCONSISTENT_STATE); this.values.set(swappedKey, [swappedValueAndIndex[0], index]); } return getValueWithOptions(this.subtype(), encode(value), options); } /** * Remove all of the elements stored within the collection. */ clear() { for (const key of this._keys) { // Set instead of remove to avoid loading the value from storage. this.values.set(key, null); } this._keys.clear(); } [Symbol.iterator]() { return new UnorderedMapIterator(this); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ createIteratorWithOptions(options) { return { [Symbol.iterator]: () => new UnorderedMapIterator(this, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options) { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. */ extend(keyValuePairs) { for (const [key, value] of keyValuePairs) { this.set(key, value); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options) { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data) { const map = new UnorderedMap(data.prefix); // reconstruct keys Vector map._keys = new Vector(`${data.prefix}u`); map._keys.length = data._keys.length; // reconstruct values LookupMap map.values = new LookupMap(`${data.prefix}m`); return map; } keys({ start, limit }) { const ret = []; if (start === undefined) { start = 0; } if (limit == undefined) { limit = this.length - start; } for (let i = start; i < start + limit; i++) { ret.push(this._keys.get(i)); } return ret; } } /** * An iterator for the UnorderedMap collection. */ class UnorderedMapIterator { /** * @param unorderedMap - The unordered map collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor(unorderedMap, options) { this.options = options; this.keys = new VectorIterator(unorderedMap._keys); this.map = unorderedMap.values; this.subtype = unorderedMap.subtype; } /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ subtype() { } next() { const key = this.keys.next(); if (key.done) { return { value: [key.value, null], done: key.done }; } const valueAndIndex = this.map.get(key.value); assert(valueAndIndex !== null, ERR_INCONSISTENT_STATE); return { done: key.done, value: [ key.value, getValueWithOptions(this.subtype(), encode(valueAndIndex[0]), this.options), ], }; } } ================================================ FILE: packages/near-sdk-js/lib/collections/unordered-set.d.ts ================================================ import { Vector, VectorIterator } from "./vector"; import { GetOptions } from "../types/collections"; /** * An unordered set that stores data in NEAR storage. */ export declare class UnorderedSet { readonly prefix: string; readonly elementIndexPrefix: string; readonly _elements: Vector; /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(prefix: string); /** * The number of elements stored in the collection. */ get length(): number; /** * Checks whether the collection is empty. */ isEmpty(): boolean; /** * Checks whether the collection contains the value. * * @param element - The value for which to check the presence. * @param options - Options for storing data. */ contains(element: DataType, options?: Pick, "serializer">): boolean; /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param element - The value to store in the collection. * @param options - Options for storing the data. */ set(element: DataType, options?: Pick, "serializer">): boolean; /** * Returns true if the element was present in the set. * * @param element - The entry to remove. * @param options - Options for retrieving and storing data. */ remove(element: DataType, options?: GetOptions): boolean; /** * Remove all of the elements stored within the collection. */ clear(options?: Pick, "serializer">): void; [Symbol.iterator](): VectorIterator; /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions; /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): DataType[]; /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements: DataType[]): void; /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array; /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: UnorderedSet): UnorderedSet; elements({ options, start, limit, }: { options?: GetOptions; start?: number; limit?: number; }): DataType[]; } ================================================ FILE: packages/near-sdk-js/lib/collections/unordered-set.js ================================================ import * as near from "../api"; import { assert, serializeValueWithOptions, ERR_INCONSISTENT_STATE, encode, } from "../utils"; import { Vector, VectorIterator } from "./vector"; function serializeIndex(index) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); return array; } function deserializeIndex(rawIndex) { const [data] = new Uint32Array(rawIndex.buffer); return data; } /** * An unordered set that stores data in NEAR storage. */ export class UnorderedSet { /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(prefix) { this.prefix = prefix; this.elementIndexPrefix = `${prefix}i`; this._elements = new Vector(`${prefix}e`); } /** * The number of elements stored in the collection. */ get length() { return this._elements.length; } /** * Checks whether the collection is empty. */ isEmpty() { return this._elements.isEmpty(); } /** * Checks whether the collection contains the value. * * @param element - The value for which to check the presence. * @param options - Options for storing data. */ contains(element, options) { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); return near.storageHasKey(indexLookup); } /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param element - The value to store in the collection. * @param options - Options for storing the data. */ set(element, options) { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); if (near.storageRead(indexLookup)) { return false; } const nextIndex = this.length; const nextIndexRaw = serializeIndex(nextIndex); near.storageWriteRaw(encode(indexLookup), nextIndexRaw); this._elements.push(element, options); return true; } /** * Returns true if the element was present in the set. * * @param element - The entry to remove. * @param options - Options for retrieving and storing data. */ remove(element, options) { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); const indexRaw = near.storageReadRaw(encode(indexLookup)); if (!indexRaw) { return false; } // If there is only one element then swap remove simply removes it without // swapping with the last element. if (this.length === 1) { near.storageRemove(indexLookup); const index = deserializeIndex(indexRaw); this._elements.swapRemove(index); return true; } // If there is more than one element then swap remove swaps it with the last // element. const lastElement = this._elements.get(this.length - 1, options); assert(!!lastElement, ERR_INCONSISTENT_STATE); near.storageRemove(indexLookup); // If the removed element was the last element from keys, then we don't need to // reinsert the lookup back. if (lastElement !== element) { const lastLookupElement = this.elementIndexPrefix + serializeValueWithOptions(lastElement, options); near.storageWriteRaw(encode(lastLookupElement), indexRaw); } const index = deserializeIndex(indexRaw); this._elements.swapRemove(index); return true; } /** * Remove all of the elements stored within the collection. */ clear(options) { for (const element of this._elements) { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); near.storageRemove(indexLookup); } this._elements.clear(); } [Symbol.iterator]() { return this._elements[Symbol.iterator](); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ createIteratorWithOptions(options) { return { [Symbol.iterator]: () => new VectorIterator(this._elements, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options) { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements) { for (const element of elements) { this.set(element); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options) { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data) { const set = new UnorderedSet(data.prefix); // reconstruct Vector const elementsPrefix = data.prefix + "e"; set._elements = new Vector(elementsPrefix); set._elements.length = data._elements.length; return set; } elements({ options, start, limit, }) { const ret = []; if (start === undefined) { start = 0; } if (limit == undefined) { limit = this.length - start; } for (let i = start; i < start + limit; i++) { ret.push(this._elements.get(i, options)); } return ret; } } ================================================ FILE: packages/near-sdk-js/lib/collections/vector.d.ts ================================================ import { GetOptions } from "../types/collections"; import { SubType } from "./subtype"; /** * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ export declare class Vector extends SubType { readonly prefix: string; length: number; /** * @param prefix - The byte prefix to use when storing elements inside this collection. * @param length - The initial length of the collection. By default 0. */ constructor(prefix: string, length?: number); /** * Checks whether the collection is empty. */ isEmpty(): boolean; /** * Get the data stored at the provided index. * * @param index - The index at which to look for the data. * @param options - Options for retrieving the data. */ get(index: number, options?: Omit, "serializer">): DataType | null; /** * Removes an element from the vector and returns it in serialized form. * The removed element is replaced by the last element of the vector. * Does not preserve ordering, but is `O(1)`. * * @param index - The index at which to remove the element. * @param options - Options for retrieving and storing the data. */ swapRemove(index: number, options?: GetOptions): DataType | null; /** * Adds data to the collection. * * @param element - The data to store. * @param options - Options for storing the data. */ push(element: DataType, options?: Pick, "serializer">): void; /** * Removes and retrieves the element with the highest index. * * @param options - Options for retrieving the data. */ pop(options?: Omit, "serializer">): DataType | null; /** * Replaces the data stored at the provided index with the provided data and returns the previously stored data. * * @param index - The index at which to replace the data. * @param element - The data to replace with. * @param options - Options for retrieving and storing the data. */ replace(index: number, element: DataType, options?: GetOptions): DataType; /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements: DataType[]): void; [Symbol.iterator](): VectorIterator; /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions; /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): DataType[]; /** * Remove all of the elements stored within the collection. */ clear(): void; /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array; /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: Vector): Vector; } /** * An iterator for the Vector collection. */ export declare class VectorIterator { private vector; private readonly options?; private current; /** * @param vector - The vector collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor(vector: Vector, options?: GetOptions); next(): { value: DataType | null; done: boolean; }; } ================================================ FILE: packages/near-sdk-js/lib/collections/vector.js ================================================ import * as near from "../api"; import { assert, getValueWithOptions, serializeValueWithOptions, ERR_INCONSISTENT_STATE, ERR_INDEX_OUT_OF_BOUNDS, str, bytes, } from "../utils"; import { SubType } from "./subtype"; function indexToKey(prefix, index) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); const key = str(array); return prefix + key; } /** * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ export class Vector extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. * @param length - The initial length of the collection. By default 0. */ constructor(prefix, length = 0) { super(); this.prefix = prefix; this.length = length; } /** * Checks whether the collection is empty. */ isEmpty() { return this.length === 0; } /** * Get the data stored at the provided index. * * @param index - The index at which to look for the data. * @param options - Options for retrieving the data. */ get(index, options) { if (index >= this.length) { return options?.defaultValue ?? null; } const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Removes an element from the vector and returns it in serialized form. * The removed element is replaced by the last element of the vector. * Does not preserve ordering, but is `O(1)`. * * @param index - The index at which to remove the element. * @param options - Options for retrieving and storing the data. */ swapRemove(index, options) { assert(index < this.length, ERR_INDEX_OUT_OF_BOUNDS); if (index + 1 === this.length) { return this.pop(options); } const key = indexToKey(this.prefix, index); const last = this.pop(options); assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(last, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Adds data to the collection. * * @param element - The data to store. * @param options - Options for storing the data. */ push(element, options) { const key = indexToKey(this.prefix, this.length); this.length += 1; near.storageWriteRaw(bytes(key), serializeValueWithOptions(element, options)); } /** * Removes and retrieves the element with the highest index. * * @param options - Options for retrieving the data. */ pop(options) { if (this.isEmpty()) { return options?.defaultValue ?? null; } const lastIndex = this.length - 1; const lastKey = indexToKey(this.prefix, lastIndex); this.length -= 1; assert(near.storageRemoveRaw(bytes(lastKey)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Replaces the data stored at the provided index with the provided data and returns the previously stored data. * * @param index - The index at which to replace the data. * @param element - The data to replace with. * @param options - Options for retrieving and storing the data. */ replace(index, element, options) { assert(index < this.length, ERR_INDEX_OUT_OF_BOUNDS); const key = indexToKey(this.prefix, index); assert(near.storageWriteRaw(bytes(key), serializeValueWithOptions(element, options)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements) { for (const element of elements) { this.push(element); } } [Symbol.iterator]() { return new VectorIterator(this); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ createIteratorWithOptions(options) { return { [Symbol.iterator]: () => new VectorIterator(this, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options) { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Remove all of the elements stored within the collection. */ clear() { for (let index = 0; index < this.length; index++) { const key = indexToKey(this.prefix, index); near.storageRemoveRaw(bytes(key)); } this.length = 0; } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options) { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data) { const vector = new Vector(data.prefix, data.length); return vector; } } /** * An iterator for the Vector collection. */ export class VectorIterator { /** * @param vector - The vector collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor(vector, options) { this.vector = vector; this.options = options; this.current = 0; } next() { if (this.current >= this.vector.length) { return { value: null, done: true }; } const value = this.vector.get(this.current, this.options); this.current += 1; return { value, done: false }; } } ================================================ FILE: packages/near-sdk-js/lib/index.d.ts ================================================ export * from "./collections"; export * from "./types"; export * as near from "./api"; export * from "./near-bindgen"; export * from "./promise"; export * from "./utils"; ================================================ FILE: packages/near-sdk-js/lib/index.js ================================================ export * from "./collections"; export * from "./types"; export * as near from "./api"; export * from "./near-bindgen"; export * from "./promise"; export * from "./utils"; ================================================ FILE: packages/near-sdk-js/lib/near-bindgen.d.ts ================================================ declare type EmptyParameterObject = Record; declare type DecoratorFunction = any>(target: object, key: string | symbol, descriptor: TypedPropertyDescriptor) => void; /** * Tells the SDK to use this function as the migration function of the contract. * The migration function will ignore te existing state. * @param _empty - An empty object. */ export declare function migrate(_empty: EmptyParameterObject): DecoratorFunction; /** * Tells the SDK to use this function as the initialization function of the contract. * * @param _empty - An empty object. */ export declare function initialize(_empty: EmptyParameterObject): DecoratorFunction; /** * Tells the SDK to expose this function as a view function. * * @param _empty - An empty object. */ export declare function view(_empty: EmptyParameterObject): DecoratorFunction; /** * Tells the SDK to expose this function as a call function. * Adds the necessary checks if the function is private or payable. * * @param options - Options to configure the function behaviour. * @param options.privateFunction - Whether the function can be called by other contracts. * @param options.payableFunction - Whether the function can accept an attached deposit. * @returns */ export declare function call(options: { privateFunction?: boolean; payableFunction?: boolean; }): DecoratorFunction; /** * The interface that a middleware has to implement in order to be used as a middleware function/class. */ interface Middleware> { /** * The method that gets called with the same arguments that are passed to the function it is wrapping. * * @param args - Arguments that will be passed to the function - immutable. */ (...args: Arguments): void; } /** * Tells the SDK to apply an array of passed in middleware to the function execution. * * @param middlewares - The middlewares to be executed. */ export declare function middleware>(...middlewares: Middleware[]): DecoratorFunction; /** * Extends this class with the methods needed to make the contract storable/serializable and readable/deserializable to and from the blockchain. * Also tells the SDK to capture and expose all view, call and initialize functions. * Tells the SDK whether the contract requires initialization and whether to use a custom serialization/deserialization function when storing/reading the state. * * @param options - Options to configure the contract behaviour. * @param options.requireInit - Whether the contract requires initialization. * @param options.serializer - Custom serializer function to use for storing the contract state. * @param options.deserializer - Custom deserializer function to use for reading the contract state. */ export declare function NearBindgen(options: { requireInit?: boolean; serializer?(value: unknown): Uint8Array; deserializer?(value: Uint8Array): unknown; }): any; declare module "./" { /** * A macro that reads the WASM code from the specified path at compile time. * * @param pathToWasm - The path to the WASM file to read code from. */ function includeBytes(pathToWasm: string): Uint8Array; } export {}; ================================================ FILE: packages/near-sdk-js/lib/near-bindgen.js ================================================ import * as near from "./api"; import { deserialize, serialize, bytes, encode, decodeObj2class, } from "./utils"; /** * Tells the SDK to use this function as the migration function of the contract. * The migration function will ignore te existing state. * @param _empty - An empty object. */ export function migrate(_empty) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (_target, _key, _descriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ) { }; } /** * Tells the SDK to use this function as the initialization function of the contract. * * @param _empty - An empty object. */ export function initialize(_empty) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (_target, _key, _descriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ) { }; } /** * Tells the SDK to expose this function as a view function. * * @param _empty - An empty object. */ export function view(_empty) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (_target, _key, _descriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ) { }; } export function call({ privateFunction = false, payableFunction = false, }) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (_target, _key, descriptor) { const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore descriptor.value = function (...args) { if (privateFunction && near.predecessorAccountId() !== near.currentAccountId()) { throw new Error("Function is private"); } if (!payableFunction && near.attachedDeposit() > 0n) { throw new Error("Function is not payable"); } return originalMethod.apply(this, args); }; }; } /** * Tells the SDK to apply an array of passed in middleware to the function execution. * * @param middlewares - The middlewares to be executed. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function middleware(...middlewares) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (_target, _key, descriptor) { const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore descriptor.value = function (...args) { try { middlewares.forEach((middleware) => middleware(...args)); } catch (error) { throw new Error(error); } return originalMethod.apply(this, args); }; }; } export function NearBindgen({ requireInit = false, serializer = serialize, deserializer = deserialize, }) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (target) => { return class extends target { static _create() { return new target(); } static _getState() { const rawState = near.storageReadRaw(bytes("STATE")); return rawState ? this._deserialize(rawState) : null; } static _saveToStorage(objectToSave) { near.storageWriteRaw(bytes("STATE"), this._serialize(objectToSave)); } static _getArgs() { return JSON.parse(near.input() || "{}"); } static _serialize(value, forReturn = false) { if (forReturn) { return encode(JSON.stringify(value, (_, value) => typeof value === "bigint" ? `${value}` : value)); } return serializer(value); } static _deserialize(value) { return deserializer(value); } static _reconstruct(classObject, plainObject) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (classObject.constructor.schema === undefined) { for (const item in classObject) { const reconstructor = classObject[item].constructor?.reconstruct; classObject[item] = reconstructor ? reconstructor(plainObject[item]) : plainObject[item]; } return classObject; } return decodeObj2class(classObject, plainObject); } static _requireInit() { return requireInit; } }; }; } ================================================ FILE: packages/near-sdk-js/lib/promise.d.ts ================================================ import { PromiseIndex } from "./utils"; import { Balance, PublicKey, AccountId, Gas, GasWeight } from "./types"; import { Nonce } from "./types/primitives"; /** * A promise action which can be executed on the NEAR blockchain. */ export declare abstract class PromiseAction { /** * The method that describes how a promise action adds it's _action_ to the promise batch with the provided index. * * @param promiseIndex - The index of the promise batch to attach the action to. */ abstract add(promiseIndex: PromiseIndex): void; } /** * A create account promise action. * * @extends {PromiseAction} */ export declare class CreateAccount extends PromiseAction { add(promiseIndex: PromiseIndex): void; } /** * A deploy contract promise action. * * @extends {PromiseAction} */ export declare class DeployContract extends PromiseAction { code: Uint8Array; /** * @param code - The code of the contract to be deployed. */ constructor(code: Uint8Array); add(promiseIndex: PromiseIndex): void; } /** * A function call promise action. * * @extends {PromiseAction} */ export declare class FunctionCall extends PromiseAction { functionName: string; args: string; amount: Balance; gas: Gas; /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor(functionName: string, args: string, amount: Balance, gas: Gas); add(promiseIndex: PromiseIndex): void; } /** * A function call raw promise action. * * @extends {PromiseAction} */ export declare class FunctionCallRaw extends PromiseAction { functionName: string; args: Uint8Array; amount: Balance; gas: Gas; /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor(functionName: string, args: Uint8Array, amount: Balance, gas: Gas); add(promiseIndex: PromiseIndex): void; } /** * A function call weight promise action. * * @extends {PromiseAction} */ export declare class FunctionCallWeight extends PromiseAction { functionName: string; args: string; amount: Balance; gas: Gas; weight: GasWeight; /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor(functionName: string, args: string, amount: Balance, gas: Gas, weight: GasWeight); add(promiseIndex: PromiseIndex): void; } /** * A function call weight raw promise action. * * @extends {PromiseAction} */ export declare class FunctionCallWeightRaw extends PromiseAction { functionName: string; args: Uint8Array; amount: Balance; gas: Gas; weight: GasWeight; /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor(functionName: string, args: Uint8Array, amount: Balance, gas: Gas, weight: GasWeight); add(promiseIndex: PromiseIndex): void; } /** * A transfer promise action. * * @extends {PromiseAction} */ export declare class Transfer extends PromiseAction { amount: Balance; /** * @param amount - The amount of NEAR to transfer. */ constructor(amount: Balance); add(promiseIndex: PromiseIndex): void; } /** * A stake promise action. * * @extends {PromiseAction} */ export declare class Stake extends PromiseAction { amount: Balance; publicKey: PublicKey; /** * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ constructor(amount: Balance, publicKey: PublicKey); add(promiseIndex: PromiseIndex): void; } /** * A add full access key promise action. * * @extends {PromiseAction} */ export declare class AddFullAccessKey extends PromiseAction { publicKey: PublicKey; nonce: Nonce; /** * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ constructor(publicKey: PublicKey, nonce: Nonce); add(promiseIndex: PromiseIndex): void; } /** * A add access key promise action. * * @extends {PromiseAction} */ export declare class AddAccessKey extends PromiseAction { publicKey: PublicKey; allowance: Balance; receiverId: AccountId; functionNames: string; nonce: Nonce; /** * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ constructor(publicKey: PublicKey, allowance: Balance, receiverId: AccountId, functionNames: string, nonce: Nonce); add(promiseIndex: PromiseIndex): void; } /** * A delete key promise action. * * @extends {PromiseAction} */ export declare class DeleteKey extends PromiseAction { publicKey: PublicKey; /** * @param publicKey - The public key to delete from the account. */ constructor(publicKey: PublicKey); add(promiseIndex: PromiseIndex): void; } /** * A delete account promise action. * * @extends {PromiseAction} */ export declare class DeleteAccount extends PromiseAction { beneficiaryId: AccountId; /** * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ constructor(beneficiaryId: AccountId); add(promiseIndex: PromiseIndex): void; } declare class PromiseSingle { accountId: AccountId; actions: PromiseAction[]; after: NearPromise | null; promiseIndex: PromiseIndex | null; constructor(accountId: AccountId, actions: PromiseAction[], after: NearPromise | null, promiseIndex: PromiseIndex | null); constructRecursively(): PromiseIndex; } export declare class PromiseJoint { promiseA: NearPromise; promiseB: NearPromise; promiseIndex: PromiseIndex | null; constructor(promiseA: NearPromise, promiseB: NearPromise, promiseIndex: PromiseIndex | null); constructRecursively(): PromiseIndex; } declare type PromiseSubtype = PromiseSingle | PromiseJoint; /** * A high level class to construct and work with NEAR promises. */ export declare class NearPromise { private subtype; private shouldReturn; /** * @param subtype - The subtype of the promise. * @param shouldReturn - Whether the promise should return. */ constructor(subtype: PromiseSubtype, shouldReturn: boolean); /** * Creates a new promise to the provided account ID. * * @param accountId - The account ID on which to call the promise. */ static new(accountId: AccountId): NearPromise; private addAction; /** * Creates a create account promise action and adds it to the current promise. */ createAccount(): NearPromise; /** * Creates a deploy contract promise action and adds it to the current promise. * * @param code - The code of the contract to be deployed. */ deployContract(code: Uint8Array): NearPromise; /** * Creates a function call promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCall(functionName: string, args: string, amount: Balance, gas: Gas): NearPromise; /** * Creates a function call raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCallRaw(functionName: string, args: Uint8Array, amount: Balance, gas: Gas): NearPromise; /** * Creates a function call weight promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeight(functionName: string, args: string, amount: Balance, gas: Gas, weight: GasWeight): NearPromise; /** * Creates a function call weight raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeightRaw(functionName: string, args: Uint8Array, amount: Balance, gas: Gas, weight: GasWeight): NearPromise; /** * Creates a transfer promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. */ transfer(amount: Balance): NearPromise; /** * Creates a stake promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ stake(amount: Balance, publicKey: PublicKey): NearPromise; /** * Creates a add full access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a full access key. */ addFullAccessKey(publicKey: PublicKey): NearPromise; /** * Creates a add full access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ addFullAccessKeyWithNonce(publicKey: PublicKey, nonce: Nonce): NearPromise; /** * Creates a add access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. */ addAccessKey(publicKey: PublicKey, allowance: Balance, receiverId: AccountId, functionNames: string): NearPromise; /** * Creates a add access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ addAccessKeyWithNonce(publicKey: PublicKey, allowance: Balance, receiverId: AccountId, functionNames: string, nonce: Nonce): NearPromise; /** * Creates a delete key promise action and adds it to the current promise. * * @param publicKey - The public key to delete from the account. */ deleteKey(publicKey: PublicKey): NearPromise; /** * Creates a delete account promise action and adds it to the current promise. * * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ deleteAccount(beneficiaryId: AccountId): NearPromise; /** * Joins the provided promise with the current promise, making the current promise a joint promise subtype. * * @param other - The promise to join with the current promise. */ and(other: NearPromise): NearPromise; /** * Adds a callback to the current promise. * * @param other - The promise to be executed as the promise. */ then(other: NearPromise): NearPromise; /** * Sets the shouldReturn field to true. */ asReturn(): NearPromise; /** * Recursively goes through the current promise to get the promise index. */ constructRecursively(): PromiseIndex; /** * Called by NearBindgen, when return object is a NearPromise instance. */ onReturn(): void; /** * Attach the promise to transaction but does not return it. The promise will be executed, but * whether it success or not will not affect the transaction result. If you want the promise fail * also makes the transaction fail, you can simply return the promise from a @call method. */ build(): PromiseIndex; } export declare type PromiseOrValue = NearPromise | T; export {}; ================================================ FILE: packages/near-sdk-js/lib/promise.js ================================================ import { assert } from "./utils"; import * as near from "./api"; /** * A promise action which can be executed on the NEAR blockchain. */ export class PromiseAction { } /** * A create account promise action. * * @extends {PromiseAction} */ export class CreateAccount extends PromiseAction { add(promiseIndex) { near.promiseBatchActionCreateAccount(promiseIndex); } } /** * A deploy contract promise action. * * @extends {PromiseAction} */ export class DeployContract extends PromiseAction { /** * @param code - The code of the contract to be deployed. */ constructor(code) { super(); this.code = code; } add(promiseIndex) { near.promiseBatchActionDeployContract(promiseIndex, this.code); } } /** * A function call promise action. * * @extends {PromiseAction} */ export class FunctionCall extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor(functionName, args, amount, gas) { super(); this.functionName = functionName; this.args = args; this.amount = amount; this.gas = gas; } add(promiseIndex) { near.promiseBatchActionFunctionCall(promiseIndex, this.functionName, this.args, this.amount, this.gas); } } /** * A function call raw promise action. * * @extends {PromiseAction} */ export class FunctionCallRaw extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor(functionName, args, amount, gas) { super(); this.functionName = functionName; this.args = args; this.amount = amount; this.gas = gas; } add(promiseIndex) { near.promiseBatchActionFunctionCallRaw(promiseIndex, this.functionName, this.args, this.amount, this.gas); } } /** * A function call weight promise action. * * @extends {PromiseAction} */ export class FunctionCallWeight extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor(functionName, args, amount, gas, weight) { super(); this.functionName = functionName; this.args = args; this.amount = amount; this.gas = gas; this.weight = weight; } add(promiseIndex) { near.promiseBatchActionFunctionCallWeight(promiseIndex, this.functionName, this.args, this.amount, this.gas, this.weight); } } /** * A function call weight raw promise action. * * @extends {PromiseAction} */ export class FunctionCallWeightRaw extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor(functionName, args, amount, gas, weight) { super(); this.functionName = functionName; this.args = args; this.amount = amount; this.gas = gas; this.weight = weight; } add(promiseIndex) { near.promiseBatchActionFunctionCallWeightRaw(promiseIndex, this.functionName, this.args, this.amount, this.gas, this.weight); } } /** * A transfer promise action. * * @extends {PromiseAction} */ export class Transfer extends PromiseAction { /** * @param amount - The amount of NEAR to transfer. */ constructor(amount) { super(); this.amount = amount; } add(promiseIndex) { near.promiseBatchActionTransfer(promiseIndex, this.amount); } } /** * A stake promise action. * * @extends {PromiseAction} */ export class Stake extends PromiseAction { /** * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ constructor(amount, publicKey) { super(); this.amount = amount; this.publicKey = publicKey; } add(promiseIndex) { near.promiseBatchActionStake(promiseIndex, this.amount, this.publicKey.data); } } /** * A add full access key promise action. * * @extends {PromiseAction} */ export class AddFullAccessKey extends PromiseAction { /** * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ constructor(publicKey, nonce) { super(); this.publicKey = publicKey; this.nonce = nonce; } add(promiseIndex) { near.promiseBatchActionAddKeyWithFullAccess(promiseIndex, this.publicKey.data, this.nonce); } } /** * A add access key promise action. * * @extends {PromiseAction} */ export class AddAccessKey extends PromiseAction { /** * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ constructor(publicKey, allowance, receiverId, functionNames, nonce) { super(); this.publicKey = publicKey; this.allowance = allowance; this.receiverId = receiverId; this.functionNames = functionNames; this.nonce = nonce; } add(promiseIndex) { near.promiseBatchActionAddKeyWithFunctionCall(promiseIndex, this.publicKey.data, this.nonce, this.allowance, this.receiverId, this.functionNames); } } /** * A delete key promise action. * * @extends {PromiseAction} */ export class DeleteKey extends PromiseAction { /** * @param publicKey - The public key to delete from the account. */ constructor(publicKey) { super(); this.publicKey = publicKey; } add(promiseIndex) { near.promiseBatchActionDeleteKey(promiseIndex, this.publicKey.data); } } /** * A delete account promise action. * * @extends {PromiseAction} */ export class DeleteAccount extends PromiseAction { /** * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ constructor(beneficiaryId) { super(); this.beneficiaryId = beneficiaryId; } add(promiseIndex) { near.promiseBatchActionDeleteAccount(promiseIndex, this.beneficiaryId); } } class PromiseSingle { constructor(accountId, actions, after, promiseIndex) { this.accountId = accountId; this.actions = actions; this.after = after; this.promiseIndex = promiseIndex; } constructRecursively() { if (this.promiseIndex !== null) { return this.promiseIndex; } const promiseIndex = this.after ? near.promiseBatchThen(this.after.constructRecursively(), this.accountId) : near.promiseBatchCreate(this.accountId); this.actions.forEach((action) => action.add(promiseIndex)); this.promiseIndex = promiseIndex; return promiseIndex; } } export class PromiseJoint { constructor(promiseA, promiseB, promiseIndex) { this.promiseA = promiseA; this.promiseB = promiseB; this.promiseIndex = promiseIndex; } constructRecursively() { if (this.promiseIndex !== null) { return this.promiseIndex; } const result = near.promiseAnd(this.promiseA.constructRecursively(), this.promiseB.constructRecursively()); this.promiseIndex = result; return result; } } /** * A high level class to construct and work with NEAR promises. */ export class NearPromise { /** * @param subtype - The subtype of the promise. * @param shouldReturn - Whether the promise should return. */ constructor(subtype, shouldReturn) { this.subtype = subtype; this.shouldReturn = shouldReturn; } /** * Creates a new promise to the provided account ID. * * @param accountId - The account ID on which to call the promise. */ static new(accountId) { const subtype = new PromiseSingle(accountId, [], null, null); return new NearPromise(subtype, false); } addAction(action) { if (this.subtype instanceof PromiseJoint) { throw new Error("Cannot add action to a joint promise."); } this.subtype.actions.push(action); return this; } /** * Creates a create account promise action and adds it to the current promise. */ createAccount() { return this.addAction(new CreateAccount()); } /** * Creates a deploy contract promise action and adds it to the current promise. * * @param code - The code of the contract to be deployed. */ deployContract(code) { return this.addAction(new DeployContract(code)); } /** * Creates a function call promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCall(functionName, args, amount, gas) { return this.addAction(new FunctionCall(functionName, args, amount, gas)); } /** * Creates a function call raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCallRaw(functionName, args, amount, gas) { return this.addAction(new FunctionCallRaw(functionName, args, amount, gas)); } /** * Creates a function call weight promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeight(functionName, args, amount, gas, weight) { return this.addAction(new FunctionCallWeight(functionName, args, amount, gas, weight)); } /** * Creates a function call weight raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeightRaw(functionName, args, amount, gas, weight) { return this.addAction(new FunctionCallWeightRaw(functionName, args, amount, gas, weight)); } /** * Creates a transfer promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. */ transfer(amount) { return this.addAction(new Transfer(amount)); } /** * Creates a stake promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ stake(amount, publicKey) { return this.addAction(new Stake(amount, publicKey)); } /** * Creates a add full access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a full access key. */ addFullAccessKey(publicKey) { return this.addFullAccessKeyWithNonce(publicKey, 0n); } /** * Creates a add full access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ addFullAccessKeyWithNonce(publicKey, nonce) { return this.addAction(new AddFullAccessKey(publicKey, nonce)); } /** * Creates a add access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. */ addAccessKey(publicKey, allowance, receiverId, functionNames) { return this.addAccessKeyWithNonce(publicKey, allowance, receiverId, functionNames, 0n); } /** * Creates a add access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ addAccessKeyWithNonce(publicKey, allowance, receiverId, functionNames, nonce) { return this.addAction(new AddAccessKey(publicKey, allowance, receiverId, functionNames, nonce)); } /** * Creates a delete key promise action and adds it to the current promise. * * @param publicKey - The public key to delete from the account. */ deleteKey(publicKey) { return this.addAction(new DeleteKey(publicKey)); } /** * Creates a delete account promise action and adds it to the current promise. * * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ deleteAccount(beneficiaryId) { return this.addAction(new DeleteAccount(beneficiaryId)); } /** * Joins the provided promise with the current promise, making the current promise a joint promise subtype. * * @param other - The promise to join with the current promise. */ and(other) { const subtype = new PromiseJoint(this, other, null); return new NearPromise(subtype, false); } /** * Adds a callback to the current promise. * * @param other - The promise to be executed as the promise. */ then(other) { assert(other.subtype instanceof PromiseSingle, "Cannot callback joint promise."); assert(other.subtype.after === null, "Cannot callback promise which is already scheduled after another"); other.subtype.after = this; return other; } /** * Sets the shouldReturn field to true. */ asReturn() { this.shouldReturn = true; return this; } /** * Recursively goes through the current promise to get the promise index. */ constructRecursively() { const result = this.subtype.constructRecursively(); if (this.shouldReturn) { near.promiseReturn(result); } return result; } /** * Called by NearBindgen, when return object is a NearPromise instance. */ onReturn() { this.asReturn().constructRecursively(); } /** * Attach the promise to transaction but does not return it. The promise will be executed, but * whether it success or not will not affect the transaction result. If you want the promise fail * also makes the transaction fail, you can simply return the promise from a @call method. */ build() { return this.constructRecursively(); } } ================================================ FILE: packages/near-sdk-js/lib/types/account_id.d.ts ================================================ /** * A string that represents a NEAR account ID. */ export declare type AccountId = string; ================================================ FILE: packages/near-sdk-js/lib/types/account_id.js ================================================ export {}; ================================================ FILE: packages/near-sdk-js/lib/types/collections.d.ts ================================================ /** * Options for retrieving and storing data in the SDK collections. */ export interface GetOptions { /** * A constructor function to call after deserializing a value. Typically this is a constructor of the class you are storing. * * @param value - The value returned from deserialization - either the provided `deserializer` or default deserialization function. */ reconstructor?(value: unknown): DataType; /** * A default value to return if the original value is not present or null. */ defaultValue?: DataType; /** * A serializer function to customize the serialization of the collection for this call. * * @param valueToSerialize - The value that will be serialized - either the `DataType` or a unknown value. */ serializer?(valueToSerialize: unknown): Uint8Array; /** * A deserializer function to customize the deserialization of values after reading from NEAR storage for this call. * * @param valueToDeserialize - The Uint8Array retrieved from NEAR storage to deserialize. */ deserializer?(valueToDeserialize: Uint8Array): unknown; } ================================================ FILE: packages/near-sdk-js/lib/types/collections.js ================================================ export {}; ================================================ FILE: packages/near-sdk-js/lib/types/gas.d.ts ================================================ /** * The Gas amount specified in yoctoNEAR. */ export declare type Gas = bigint; /** * One TGas - Tera Gas. 10^12 yoctoNEAR. */ export declare const ONE_TERA_GAS: Gas; ================================================ FILE: packages/near-sdk-js/lib/types/gas.js ================================================ /** * One TGas - Tera Gas. 10^12 yoctoNEAR. */ export const ONE_TERA_GAS = 1000000000000n; ================================================ FILE: packages/near-sdk-js/lib/types/index.d.ts ================================================ export * from "./account_id"; export * from "./gas"; export * from "./primitives"; export * from "./public_key"; export * from "./vm_types"; ================================================ FILE: packages/near-sdk-js/lib/types/index.js ================================================ export * from "./account_id"; export * from "./gas"; export * from "./primitives"; export * from "./public_key"; export * from "./vm_types"; ================================================ FILE: packages/near-sdk-js/lib/types/primitives.d.ts ================================================ /** * The amount of storage used in yoctoNEAR. */ export declare type StorageUsage = bigint; /** * A large integer representing the block height. */ export declare type BlockHeight = bigint; /** * A large integer representing the epoch height. */ export declare type EpochHeight = bigint; /** * The amount of tokens in yoctoNEAR. */ export declare type Balance = bigint; /** * A large integer representing the nonce. */ export declare type Nonce = bigint; /** * The amount of Gas Weight in integers - whole numbers. */ export declare type GasWeight = bigint; /** * One yoctoNEAR. 10^-24 NEAR. */ export declare const ONE_YOCTO: Balance; /** * One NEAR. 1 NEAR = 10^24 yoctoNEAR. */ export declare const ONE_NEAR: Balance; ================================================ FILE: packages/near-sdk-js/lib/types/primitives.js ================================================ /** * One yoctoNEAR. 10^-24 NEAR. */ export const ONE_YOCTO = 1n; /** * One NEAR. 1 NEAR = 10^24 yoctoNEAR. */ export const ONE_NEAR = 1000000000000000000000000n; ================================================ FILE: packages/near-sdk-js/lib/types/public_key.d.ts ================================================ export declare enum CurveType { ED25519 = 0, SECP256K1 = 1 } export declare function curveTypeFromStr(value: string): CurveType; export declare class ParsePublicKeyError extends Error { } export declare class InvalidLengthError extends ParsePublicKeyError { length: number; expectedLength: number; constructor(length: number, expectedLength: number); } export declare class Base58Error extends ParsePublicKeyError { error: string; constructor(error: string); } export declare class UnknownCurve extends ParsePublicKeyError { constructor(); } /** * A abstraction on top of the NEAR public key string. * Public key in a binary format with base58 string serialization with human-readable curve. * The key types currently supported are `secp256k1` and `ed25519`. */ export declare class PublicKey { /** * The actual value of the public key. */ data: Uint8Array; private type; /** * @param data - The string you want to create a PublicKey from. */ constructor(data: Uint8Array); /** * The curve type of the public key. */ curveType(): CurveType; /** * Create a public key from a public key string. * * @param publicKeyString - The public key string you want to create a PublicKey from. */ static fromString(publicKeyString: string): PublicKey; } ================================================ FILE: packages/near-sdk-js/lib/types/public_key.js ================================================ import { base58 } from "@scure/base"; import { concat } from "../utils"; export var CurveType; (function (CurveType) { CurveType[CurveType["ED25519"] = 0] = "ED25519"; CurveType[CurveType["SECP256K1"] = 1] = "SECP256K1"; })(CurveType || (CurveType = {})); var DataLength; (function (DataLength) { DataLength[DataLength["ED25519"] = 32] = "ED25519"; DataLength[DataLength["SECP256K1"] = 64] = "SECP256K1"; })(DataLength || (DataLength = {})); function getCurveType(curveType) { switch (curveType) { case CurveType.ED25519: case CurveType.SECP256K1: return curveType; default: throw new UnknownCurve(); } } function dataLength(curveType) { switch (curveType) { case CurveType.ED25519: case CurveType.SECP256K1: return { [CurveType.ED25519]: DataLength.ED25519, [CurveType.SECP256K1]: DataLength.SECP256K1, }[curveType]; default: throw new UnknownCurve(); } } function splitKeyTypeData(value) { const idx = value.indexOf(":"); if (idx >= 0) { return [ curveTypeFromStr(value.substring(0, idx)), value.substring(idx + 1), ]; } else { return [CurveType.ED25519, value]; } } export function curveTypeFromStr(value) { switch (value) { case "ed25519": return CurveType.ED25519; case "secp256k1": return CurveType.SECP256K1; default: throw new UnknownCurve(); } } export class ParsePublicKeyError extends Error { } export class InvalidLengthError extends ParsePublicKeyError { constructor(length, expectedLength) { super(`Invalid length: ${length}. Expected: ${expectedLength}`); this.length = length; this.expectedLength = expectedLength; } } export class Base58Error extends ParsePublicKeyError { constructor(error) { super(`Base58 error: ${error}`); this.error = error; } } export class UnknownCurve extends ParsePublicKeyError { constructor() { super("Unknown curve"); } } /** * A abstraction on top of the NEAR public key string. * Public key in a binary format with base58 string serialization with human-readable curve. * The key types currently supported are `secp256k1` and `ed25519`. */ export class PublicKey { /** * @param data - The string you want to create a PublicKey from. */ constructor(data) { const curveLenght = dataLength(data[0]); if (data.length !== curveLenght + 1) { throw new InvalidLengthError(data.length, curveLenght + 1); } this.type = getCurveType(data[0]); this.data = data; } /** * The curve type of the public key. */ curveType() { return this.type; } /** * Create a public key from a public key string. * * @param publicKeyString - The public key string you want to create a PublicKey from. */ static fromString(publicKeyString) { const [curve, keyData] = splitKeyTypeData(publicKeyString); let data; try { data = base58.decode(keyData); } catch (error) { throw new Base58Error(error.message); } return new PublicKey(concat(new Uint8Array([curve]), data)); } } ================================================ FILE: packages/near-sdk-js/lib/types/vm_types.d.ts ================================================ /** * The index for NEAR receipts. */ export declare type ReceiptIndex = bigint; /** * The index for iterators. */ export declare type IteratorIndex = bigint; /** * A Promise result in near can be one of: * - NotReady = 0 - the promise you are specifying is still not ready, not yet failed nor successful. * - Successful = 1 - the promise has been successfully executed and you can retrieve the resulting value. * - Failed = 2 - the promise execution has failed. */ export declare enum PromiseResult { NotReady = 0, Successful = 1, Failed = 2 } /** * A promise error can either be due to the promise failing or not yet being ready. */ export declare enum PromiseError { Failed = 0, NotReady = 1 } ================================================ FILE: packages/near-sdk-js/lib/types/vm_types.js ================================================ /** * A Promise result in near can be one of: * - NotReady = 0 - the promise you are specifying is still not ready, not yet failed nor successful. * - Successful = 1 - the promise has been successfully executed and you can retrieve the resulting value. * - Failed = 2 - the promise execution has failed. */ export var PromiseResult; (function (PromiseResult) { PromiseResult[PromiseResult["NotReady"] = 0] = "NotReady"; PromiseResult[PromiseResult["Successful"] = 1] = "Successful"; PromiseResult[PromiseResult["Failed"] = 2] = "Failed"; })(PromiseResult || (PromiseResult = {})); /** * A promise error can either be due to the promise failing or not yet being ready. */ export var PromiseError; (function (PromiseError) { PromiseError[PromiseError["Failed"] = 0] = "Failed"; PromiseError[PromiseError["NotReady"] = 1] = "NotReady"; })(PromiseError || (PromiseError = {})); ================================================ FILE: packages/near-sdk-js/lib/utils.d.ts ================================================ import { GetOptions } from "./types/collections"; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; uint8array_to_utf8_string(a: Uint8Array): string; latin1_string_to_uint8array(s: string): Uint8Array; utf8_string_to_uint8array(s: string): Uint8Array; } /** * A PromiseIndex which represents the ID of a NEAR Promise. */ export declare type PromiseIndex = number | bigint; /** * A number that specifies the amount of NEAR in yoctoNEAR. */ export declare type NearAmount = number | bigint; /** * A number that specifies the ID of a register in the NEAR WASM virtual machine. */ export declare type Register = number | bigint; export declare const ERR_INCONSISTENT_STATE = "The collection is an inconsistent state. Did previous smart contract execution terminate unexpectedly?"; export declare const ERR_INDEX_OUT_OF_BOUNDS = "Index out of bounds"; /** * Concat two Uint8Array * @param array1 * @param array2 * @returns the concatenation of two array */ export declare function concat(array1: Uint8Array, array2: Uint8Array): Uint8Array; /** * Asserts that the expression passed to the function is truthy, otherwise throws a new Error with the provided message. * * @param expression - The expression to be asserted. * @param message - The error message to be printed. */ export declare function assert(expression: unknown, message: string): asserts expression; export declare type Mutable = { -readonly [P in keyof T]: T[P]; }; export declare function getValueWithOptions(subDatatype: unknown, value: Uint8Array | null, options?: Omit, "serializer">): DataType | null; export declare function serializeValueWithOptions(value: DataType, { serializer }?: Pick, "serializer">): Uint8Array; export declare function serialize(valueToSerialize: unknown): Uint8Array; export declare function deserialize(valueToDeserialize: Uint8Array): unknown; export declare function decodeObj2class(class_instance: any, obj: any): any; /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). * * @param accountId - The Account ID string you want to validate. */ export declare function validateAccountId(accountId: string): boolean; /** * A subset of NodeJS TextEncoder API */ export declare class TextEncoder { encode(s: string): Uint8Array; } /** * A subset of NodeJS TextDecoder API. Only support utf-8 and latin1 encoding. */ export declare class TextDecoder { encoding: string; constructor(encoding?: string); decode(a: Uint8Array): string; } /** * Convert a string to Uint8Array, each character must have a char code between 0-255. * @param s - string that with only Latin1 character to convert * @returns result Uint8Array */ export declare function bytes(s: string): Uint8Array; /** * Convert a Uint8Array to string, each uint8 to the single character of that char code * @param a - Uint8Array to convert * @returns result string */ export declare function str(a: Uint8Array): string; /** * Encode the string to Uint8Array with UTF-8 encoding * @param s - String to encode * @returns result Uint8Array */ export declare function encode(s: string): Uint8Array; /** * Decode the Uint8Array to string in UTF-8 encoding * @param a - array to decode * @returns result string */ export declare function decode(a: Uint8Array): string; /** * When implemented, allow object to be stored as collection key */ export interface IntoStorageKey { into_storage_key(): string; } ================================================ FILE: packages/near-sdk-js/lib/utils.js ================================================ import { cloneDeep } from "lodash-es"; const TYPE_KEY = "typeInfo"; var TypeBrand; (function (TypeBrand) { TypeBrand["BIGINT"] = "bigint"; TypeBrand["DATE"] = "date"; })(TypeBrand || (TypeBrand = {})); export const ERR_INCONSISTENT_STATE = "The collection is an inconsistent state. Did previous smart contract execution terminate unexpectedly?"; export const ERR_INDEX_OUT_OF_BOUNDS = "Index out of bounds"; const ACCOUNT_ID_REGEX = /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/; /** * Concat two Uint8Array * @param array1 * @param array2 * @returns the concatenation of two array */ export function concat(array1, array2) { const mergedArray = new Uint8Array(array1.length + array2.length); mergedArray.set(array1); mergedArray.set(array2, array1.length); return mergedArray; } /** * Asserts that the expression passed to the function is truthy, otherwise throws a new Error with the provided message. * * @param expression - The expression to be asserted. * @param message - The error message to be printed. */ export function assert(expression, message) { if (!expression) { throw new Error("assertion failed: " + message); } } export function getValueWithOptions(subDatatype, value, options = {}) { if (value === null) { return options?.defaultValue ?? null; } const deserializer = options.deserializer || deserialize; // here is an obj let deserialized = deserializer(value); if (deserialized === undefined || deserialized === null) { return options?.defaultValue ?? null; } if (options?.reconstructor) { // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} const collection = options.reconstructor(deserialized); if (subDatatype !== undefined && // eslint-disable-next-line no-prototype-builtins subDatatype.hasOwnProperty("class") && // eslint-disable-next-line no-prototype-builtins subDatatype["class"].hasOwnProperty("value")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore collection.subtype = function () { // example: {class: UnorderedMap, value: UnorderedMap} return subDatatype["class"]["value"]; }; } return collection; } // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} if (subDatatype !== undefined) { // subtype info is a class constructor, Such as Car if (typeof subDatatype === "function") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore deserialized = decodeObj2class(new subDatatype(), deserialized); } else if (typeof subDatatype === "object") { // normal collections of array, map; subtype will be: // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins if (subDatatype.hasOwnProperty("map")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { if (subDatatype["map"]["value"] !== "string") { deserialized[mkey] = decodeObj2class(new subDatatype["map"]["value"](), value[mkey]); } } // eslint-disable-next-line no-prototype-builtins } else if (subDatatype.hasOwnProperty("array")) { const new_vec = []; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { if (subDatatype["array"]["value"] !== "string") { new_vec.push(decodeObj2class(new subDatatype["array"]["value"](), value[k])); } } deserialized = new_vec; // eslint-disable-next-line no-prototype-builtins } } } return deserialized; } export function serializeValueWithOptions(value, { serializer } = { serializer: serialize, }) { return serializer(value); } export function serialize(valueToSerialize) { return encode(JSON.stringify(valueToSerialize, function (key, value) { if (typeof value === "bigint") { return { value: value.toString(), [TYPE_KEY]: TypeBrand.BIGINT, }; } if (typeof this[key] === "object" && this[key] !== null && this[key] instanceof Date) { return { value: this[key].toISOString(), [TYPE_KEY]: TypeBrand.DATE, }; } return value; })); } export function deserialize(valueToDeserialize) { return JSON.parse(decode(valueToDeserialize), (_, value) => { if (value !== null && typeof value === "object" && Object.keys(value).length === 2 && Object.keys(value).every((key) => ["value", TYPE_KEY].includes(key))) { switch (value[TYPE_KEY]) { case TypeBrand.BIGINT: return BigInt(value["value"]); case TypeBrand.DATE: return new Date(value["value"]); } } return value; }); } export function decodeObj2class(class_instance, obj) { if (typeof obj != "object" || typeof obj === "bigint" || obj instanceof Date || class_instance.constructor.schema === undefined) { return obj; } let key; for (key in obj) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const value = obj[key]; if (typeof value == "object") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins if (ty !== undefined && ty.hasOwnProperty("map")) { for (const mkey in value) { if (ty["map"]["value"] === "string") { class_instance[key][mkey] = value[mkey]; } else { class_instance[key][mkey] = decodeObj2class(new ty["map"]["value"](), value[mkey]); } } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("array")) { for (const k in value) { if (ty["array"]["value"] === "string") { class_instance[key].push(value[k]); } else { class_instance[key].push(decodeObj2class(new ty["array"]["value"](), value[k])); } } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("class")) { // => nested_lookup_recordes: {class: UnorderedMap, value: {class: LookupMap }}, class_instance[key] = ty["class"].reconstruct(obj[key]); // eslint-disable-next-line no-prototype-builtins if (ty.hasOwnProperty("value")) { const subtype_value = ty["value"]; class_instance[key].subtype = function () { // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} // example: UnorderedMap or {class: UnorderedMap, value: 'string'} return subtype_value; }; } } else if (ty !== undefined && typeof ty.reconstruct === "function") { class_instance[key] = ty.reconstruct(obj[key]); } else { // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } else { class_instance[key] = obj[key]; } } const instance_tmp = cloneDeep(class_instance); for (key in obj) { if (typeof class_instance[key] == "object" && !(class_instance[key] instanceof Date)) { class_instance[key] = instance_tmp[key]; } } return class_instance; } /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). * * @param accountId - The Account ID string you want to validate. */ export function validateAccountId(accountId) { return (accountId.length >= 2 && accountId.length <= 64 && ACCOUNT_ID_REGEX.test(accountId)); } /** * A subset of NodeJS TextEncoder API */ export class TextEncoder { encode(s) { return env.utf8_string_to_uint8array(s); } } /** * A subset of NodeJS TextDecoder API. Only support utf-8 and latin1 encoding. */ export class TextDecoder { constructor(encoding = "utf-8") { this.encoding = encoding; } decode(a) { if (this.encoding == "utf-8") { return env.uint8array_to_utf8_string(a); } else if (this.encoding == "latin1") { return env.uint8array_to_latin1_string(a); } else { throw new Error("unsupported encoding: " + this.encoding); } } } /** * Convert a string to Uint8Array, each character must have a char code between 0-255. * @param s - string that with only Latin1 character to convert * @returns result Uint8Array */ export function bytes(s) { return env.latin1_string_to_uint8array(s); } /** * Convert a Uint8Array to string, each uint8 to the single character of that char code * @param a - Uint8Array to convert * @returns result string */ export function str(a) { return env.uint8array_to_latin1_string(a); } /** * Encode the string to Uint8Array with UTF-8 encoding * @param s - String to encode * @returns result Uint8Array */ export function encode(s) { return env.utf8_string_to_uint8array(s); } /** * Decode the Uint8Array to string in UTF-8 encoding * @param a - array to decode * @returns result string */ export function decode(a) { return env.uint8array_to_utf8_string(a); } ================================================ FILE: packages/near-sdk-js/lib/version.d.ts ================================================ export declare const LIB_VERSION: string; ================================================ FILE: packages/near-sdk-js/lib/version.js ================================================ import * as fs from "fs"; import { fileURLToPath } from "url"; const PACKAGE_JSON = JSON.parse(fs.readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf-8")); export const LIB_VERSION = PACKAGE_JSON["version"]; ================================================ FILE: packages/near-sdk-js/package.json ================================================ { "name": "near-sdk-js", "version": "2.0.0", "description": "High Level JavaScript SDK for building smart contracts on NEAR", "main": "lib/index.js", "types": "lib/index.d.ts", "type": "module", "repository": { "type": "git", "url": "git+https://github.com/near/near-sdk-js.git" }, "homepage": "https://github.com/near/near-sdk-js", "keywords": [ "JS", "JavaScript", "NEAR", "SDK", "contract", "smart", "smart-contract" ], "license": "(MIT AND Apache-2.0)", "scripts": { "build": "tsc -p ./tsconfig.json", "lint": "eslint --fix .", "format": "prettier --write .", "preinstall": "npx only-allow pnpm", "postinstall": "node lib/cli/post-install.js" }, "bin": { "near-sdk-js": "lib/cli/cli.js" }, "author": "Near Inc ", "dependencies": { "@babel/core": "7.23.5", "@babel/plugin-proposal-decorators": "7.23.5", "@babel/preset-typescript": "7.23.3", "@babel/types": "7.23.5", "@rollup/plugin-babel": "5.3.1", "@rollup/plugin-commonjs": "21.1.0", "@rollup/plugin-node-resolve": "13.3.0", "@scure/base": "1.1.7", "@types/estree": "1.0.5", "commander": "9.5.0", "json-schema": "0.4.0", "lodash-es": "4.17.21", "near-abi": "0.1.1", "near-typescript-json-schema": "0.55.0", "rollup": "2.79.1", "rollup-plugin-sourcemaps": "0.6.3", "signale": "1.4.0", "ts-morph": "16.0.0", "typescript": "4.7.4" }, "files": [ "builder", "lib/*.js", "lib/*.d.ts", "lib/collections/*", "lib/types/*", "lib/cli/*.js", "lib/cli/*.d.ts", "lib/cli/build-tools/*" ], "devDependencies": { "@rollup/plugin-typescript": "8.5.0", "@types/babel__core": "7.20.5", "@types/babel__traverse": "7.20.4", "@types/eslint": "9.6.0", "@types/node": "17.0.45", "@types/rollup": "0.54.0", "@types/signale": "1.4.7", "@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/parser": "5.62.0", "chalk": "5.3.0", "eslint": "8.54.0", "eslint-config-prettier": "8.10.0", "json5": "2.2.3", "npm-run-all": "4.1.5", "prettier": "2.8.8", "typescript": "4.7.4" } } ================================================ FILE: packages/near-sdk-js/src/api.ts ================================================ import { assert, NearAmount, PromiseIndex, Register, str, encode, decode, } from "./utils"; import { GasWeight, PromiseResult } from "./types"; const U64_MAX = 2n ** 64n - 1n; const EVICTED_REGISTER = U64_MAX - 1n; // Interface available in QuickJS interface Env { // Panic panic_utf8(message: Uint8Array): never; // Logging log(message: string): void; log_utf8(message: Uint8Array): void; log_utf16(message: Uint8Array): void; // Read from register read_register(register: Register): Uint8Array; // Storage storage_read(key: Uint8Array, register: Register): bigint; storage_has_key(key: Uint8Array): bigint; storage_write(key: Uint8Array, value: Uint8Array, register: Register): bigint; storage_remove(key: Uint8Array, register: Register): bigint; storage_usage(): bigint; // Caller methods signer_account_id(register: Register): void; signer_account_pk(register: Register): void; attached_deposit(): bigint; predecessor_account_id(register: Register): void; input(register: Register): void; // Account data account_balance(): bigint; account_locked_balance(): bigint; current_account_id(register: Register): void; validator_stake(accountId: string): bigint; validator_total_stake(): bigint; // Blockchain info block_index(): bigint; block_timestamp(): bigint; epoch_height(): bigint; // Gas prepaid_gas(): bigint; used_gas(): bigint; // Helper methods and cryptography value_return(value: Uint8Array): void; random_seed(register: Register): void; sha256(value: Uint8Array, register: Register): void; keccak256(value: Uint8Array, register: Register): void; keccak512(value: Uint8Array, register: Register): void; ripemd160(value: Uint8Array, register: Register): void; ecrecover( hash: Uint8Array, sig: Uint8Array, v: number, malleabilityFlag: number, register: Register ): bigint; alt_bn128_g1_multiexp(value: Uint8Array, register: Register): void; alt_bn128_g1_sum(value: Uint8Array, register: Register): void; alt_bn128_pairing_check(value: Uint8Array): bigint; // Promises promise_create( accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): bigint; promise_then( promiseIndex: bigint, accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): bigint; promise_and(...promiseIndexes: bigint[]): bigint; promise_batch_create(accountId: string): bigint; promise_batch_then(promiseIndex: bigint, accountId: string): bigint; promise_batch_action_create_account(promiseIndex: bigint): void; promise_batch_action_deploy_contract( promiseIndex: bigint, code: Uint8Array ): void; promise_batch_action_function_call( promiseIndex: bigint, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): void; promise_batch_action_transfer(promiseIndex: bigint, amount: NearAmount): void; promise_batch_action_stake( promiseIndex: bigint, amount: NearAmount, publicKey: Uint8Array ): void; promise_batch_action_add_key_with_full_access( promiseIndex: bigint, publicKey: Uint8Array, nonce: number | bigint ): void; promise_batch_action_add_key_with_function_call( promiseIndex: bigint, publicKey: Uint8Array, nonce: number | bigint, allowance: NearAmount, receiverId: string, methodNames: string ): void; promise_batch_action_delete_key( promiseIndex: bigint, publicKey: Uint8Array ): void; promise_batch_action_delete_account( promiseIndex: bigint, beneficiaryId: string ): void; promise_batch_action_function_call_weight( promiseIndex: bigint, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount, weight: GasWeight ): void; promise_results_count(): bigint; promise_result(promiseIndex: bigint, register: Register): PromiseResult; promise_return(promiseIndex: bigint): void; // These are exported C functions that not part of NEAR VM Logic (host functions) uint8array_to_latin1_string(a: Uint8Array): string; uint8array_to_utf8_string(a: Uint8Array): string; latin1_string_to_uint8array(s: string): Uint8Array; utf8_string_to_uint8array(s: string): Uint8Array; } declare const env: Env; /** * Logs parameters in the NEAR WASM virtual machine. * * @param params - Parameters to log. */ export function log(...params: unknown[]): void { env.log( params.reduce((accumulated, parameter, index) => { // Stringify undefined const param = parameter === undefined ? "undefined" : parameter; // Convert Objects to strings and convert to string const stringified = typeof param === "object" ? JSON.stringify(param) : `${param}`; if (index === 0) { return stringified; } return `${accumulated} ${stringified}`; }, "") ); } /** * Returns the account ID of the account that signed the transaction. * Can only be called in a call or initialize function. */ export function signerAccountId(): string { env.signer_account_id(0); return str(env.read_register(0)); } /** * Returns the public key of the account that signed the transaction. * Can only be called in a call or initialize function. */ export function signerAccountPk(): Uint8Array { env.signer_account_pk(0); return env.read_register(0); } /** * Returns the account ID of the account that called the function. * Can only be called in a call or initialize function. */ export function predecessorAccountId(): string { env.predecessor_account_id(0); return str(env.read_register(0)); } /** * Returns the account ID of the current contract - the contract that is being executed. */ export function currentAccountId(): string { env.current_account_id(0); return str(env.read_register(0)); } /** * Returns the current block index. */ export function blockIndex(): bigint { return env.block_index(); } /** * Returns the current block height. */ export function blockHeight(): bigint { return blockIndex(); } /** * Returns the current block timestamp. */ export function blockTimestamp(): bigint { return env.block_timestamp(); } /** * Returns the current epoch height. */ export function epochHeight(): bigint { return env.epoch_height(); } /** * Returns the amount of NEAR attached to this function call. * Can only be called in payable functions. */ export function attachedDeposit(): bigint { return env.attached_deposit(); } /** * Returns the amount of Gas that was attached to this function call. */ export function prepaidGas(): bigint { return env.prepaid_gas(); } /** * Returns the amount of Gas that has been used by this function call until now. */ export function usedGas(): bigint { return env.used_gas(); } /** * Returns the current account's account balance. */ export function accountBalance(): bigint { return env.account_balance(); } /** * Returns the current account's locked balance. */ export function accountLockedBalance(): bigint { return env.account_locked_balance(); } /** * Reads the value from NEAR storage that is stored under the provided key. * * @param key - The key to read from storage. */ export function storageReadRaw(key: Uint8Array): Uint8Array | null { const returnValue = env.storage_read(key, 0); if (returnValue !== 1n) { return null; } return env.read_register(0); } /** * Reads the utf-8 string value from NEAR storage that is stored under the provided key. * * @param key - The utf-8 string key to read from storage. */ export function storageRead(key: string): string | null { const ret = storageReadRaw(encode(key)); if (ret !== null) { return decode(ret); } return null; } /** * Checks for the existence of a value under the provided key in NEAR storage. * * @param key - The key to check for in storage. */ export function storageHasKeyRaw(key: Uint8Array): boolean { return env.storage_has_key(key) === 1n; } /** * Checks for the existence of a value under the provided utf-8 string key in NEAR storage. * * @param key - The utf-8 string key to check for in storage. */ export function storageHasKey(key: string): boolean { return storageHasKeyRaw(encode(key)); } /** * Get the last written or removed value from NEAR storage. */ export function storageGetEvictedRaw(): Uint8Array { return env.read_register(EVICTED_REGISTER); } /** * Get the last written or removed value from NEAR storage as utf-8 string. */ export function storageGetEvicted(): string { return decode(storageGetEvictedRaw()); } /** * Returns the current accounts NEAR storage usage. */ export function storageUsage(): bigint { return env.storage_usage(); } /** * Writes the provided bytes to NEAR storage under the provided key. * * @param key - The key under which to store the value. * @param value - The value to store. */ export function storageWriteRaw(key: Uint8Array, value: Uint8Array): boolean { return env.storage_write(key, value, EVICTED_REGISTER) === 1n; } /** * Writes the provided utf-8 string to NEAR storage under the provided key. * * @param key - The utf-8 string key under which to store the value. * @param value - The utf-8 string value to store. */ export function storageWrite(key: string, value: string): boolean { return storageWriteRaw(encode(key), encode(value)); } /** * Removes the value of the provided key from NEAR storage. * * @param key - The key to be removed. */ export function storageRemoveRaw(key: Uint8Array): boolean { return env.storage_remove(key, EVICTED_REGISTER) === 1n; } /** * Removes the value of the provided utf-8 string key from NEAR storage. * * @param key - The utf-8 string key to be removed. */ export function storageRemove(key: string): boolean { return storageRemoveRaw(encode(key)); } /** * Returns the cost of storing 0 Byte on NEAR storage. */ export function storageByteCost(): bigint { return 10_000_000_000_000_000_000n; } /** * Returns the arguments passed to the current smart contract call. */ export function inputRaw(): Uint8Array { env.input(0); return env.read_register(0); } /** * Returns the arguments passed to the current smart contract call as utf-8 string. */ export function input(): string { return decode(inputRaw()); } /** * Returns the value from the NEAR WASM virtual machine. * * @param value - The value to return. */ export function valueReturnRaw(value: Uint8Array): void { env.value_return(value); } /** * Returns the utf-8 string value from the NEAR WASM virtual machine. * * @param value - The utf-8 string value to return. */ export function valueReturn(value: string): void { valueReturnRaw(encode(value)); } /** * Returns a random string of bytes. */ export function randomSeed(): Uint8Array { env.random_seed(0); return env.read_register(0); } /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export function promiseCreateRaw( accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): PromiseIndex { return env.promise_create( accountId, methodName, args, amount, gas ) as unknown as PromiseIndex; } /** * Create a NEAR promise call to a contract on the blockchain. * * @param accountId - The account ID of the target contract. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR attached to the call. * @param gas - The amount of Gas attached to the call. */ export function promiseCreate( accountId: string, methodName: string, args: string, amount: NearAmount, gas: NearAmount ): PromiseIndex { return promiseCreateRaw(accountId, methodName, encode(args), amount, gas); } /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseThenRaw( promiseIndex: PromiseIndex, accountId: string, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): PromiseIndex { return env.promise_then( promiseIndex as unknown as bigint, accountId, methodName, args, amount, gas ) as unknown as PromiseIndex; } /** * Attach a callback NEAR promise to be executed after a provided promise. * * @param promiseIndex - The promise after which to call the callback. * @param accountId - The account ID of the contract to perform the callback on. * @param methodName - The name of the method to call. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseThen( promiseIndex: PromiseIndex, accountId: string, methodName: string, args: string, amount: NearAmount, gas: NearAmount ): PromiseIndex { return promiseThenRaw( promiseIndex, accountId, methodName, encode(args), amount, gas ); } /** * Join an arbitrary array of NEAR promises. * * @param promiseIndexes - An arbitrary array of NEAR promise indexes to join. */ export function promiseAnd(...promiseIndexes: PromiseIndex[]): PromiseIndex { return env.promise_and( ...(promiseIndexes as unknown as bigint[]) ) as unknown as PromiseIndex; } /** * Create a NEAR promise which will have multiple promise actions inside. * * @param accountId - The account ID of the target contract. */ export function promiseBatchCreate(accountId: string): PromiseIndex { return env.promise_batch_create(accountId) as unknown as PromiseIndex; } /** * Attach a callback NEAR promise to a batch of NEAR promise actions. * * @param promiseIndex - The NEAR promise index of the batch. * @param accountId - The account ID of the target contract. */ export function promiseBatchThen( promiseIndex: PromiseIndex, accountId: string ): PromiseIndex { return env.promise_batch_then( promiseIndex as unknown as bigint, accountId ) as unknown as PromiseIndex; } /** * Attach a create account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a create account action to. */ export function promiseBatchActionCreateAccount( promiseIndex: PromiseIndex ): void { env.promise_batch_action_create_account(promiseIndex as unknown as bigint); } /** * Attach a deploy contract promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a deploy contract action to. * @param code - The WASM byte code of the contract to be deployed. */ export function promiseBatchActionDeployContract( promiseIndex: PromiseIndex, code: Uint8Array ): void { env.promise_batch_action_deploy_contract( promiseIndex as unknown as bigint, code ); } /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseBatchActionFunctionCallRaw( promiseIndex: PromiseIndex, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount ): void { env.promise_batch_action_function_call( promiseIndex as unknown as bigint, methodName, args, amount, gas ); } /** * Attach a function call promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ export function promiseBatchActionFunctionCall( promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount ): void { promiseBatchActionFunctionCallRaw( promiseIndex, methodName, encode(args), amount, gas ); } /** * Attach a transfer promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a transfer action to. * @param amount - The amount of NEAR to transfer. */ export function promiseBatchActionTransfer( promiseIndex: PromiseIndex, amount: NearAmount ): void { env.promise_batch_action_transfer(promiseIndex as unknown as bigint, amount); } /** * Attach a stake promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a stake action to. * @param amount - The amount of NEAR to stake. * @param publicKey - The public key with which to stake. */ export function promiseBatchActionStake( promiseIndex: PromiseIndex, amount: NearAmount, publicKey: Uint8Array ): void { env.promise_batch_action_stake( promiseIndex as unknown as bigint, amount, publicKey ); } /** * Attach a add full access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add full access key action to. * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ export function promiseBatchActionAddKeyWithFullAccess( promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint ): void { env.promise_batch_action_add_key_with_full_access( promiseIndex as unknown as bigint, publicKey, nonce ); } /** * Attach a add access key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a add access key action to. * @param publicKey - The public key to add. * @param nonce - The nonce to use. * @param allowance - The allowance of the access key. * @param receiverId - The account ID of the receiver. * @param methodNames - The names of the method to allow the key for. */ export function promiseBatchActionAddKeyWithFunctionCall( promiseIndex: PromiseIndex, publicKey: Uint8Array, nonce: number | bigint, allowance: NearAmount, receiverId: string, methodNames: string ): void { env.promise_batch_action_add_key_with_function_call( promiseIndex as unknown as bigint, publicKey, nonce, allowance, receiverId, methodNames ); } /** * Attach a delete key promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete key action to. * @param publicKey - The public key to delete. */ export function promiseBatchActionDeleteKey( promiseIndex: PromiseIndex, publicKey: Uint8Array ): void { env.promise_batch_action_delete_key( promiseIndex as unknown as bigint, publicKey ); } /** * Attach a delete account promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a delete account action to. * @param beneficiaryId - The account ID of the beneficiary - the account that receives the remaining amount of NEAR. */ export function promiseBatchActionDeleteAccount( promiseIndex: PromiseIndex, beneficiaryId: string ): void { env.promise_batch_action_delete_account( promiseIndex as unknown as bigint, beneficiaryId ); } /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export function promiseBatchActionFunctionCallWeightRaw( promiseIndex: PromiseIndex, methodName: string, args: Uint8Array, amount: NearAmount, gas: NearAmount, weight: GasWeight ): void { env.promise_batch_action_function_call_weight( promiseIndex as unknown as bigint, methodName, args, amount, gas, weight ); } /** * Attach a function call with weight promise action to the NEAR promise index with the provided promise index. * * @param promiseIndex - The index of the promise to attach a function call with weight action to. * @param methodName - The name of the method to be called. * @param args - The utf-8 string arguments to call the method with. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ export function promiseBatchActionFunctionCallWeight( promiseIndex: PromiseIndex, methodName: string, args: string, amount: NearAmount, gas: NearAmount, weight: GasWeight ): void { promiseBatchActionFunctionCallWeightRaw( promiseIndex, methodName, encode(args), amount, gas, weight ); } /** * The number of promise results available. */ export function promiseResultsCount(): bigint { return env.promise_results_count(); } /** * Returns the result of the NEAR promise for the passed promise index. * * @param promiseIndex - The index of the promise to return the result for. */ export function promiseResultRaw(promiseIndex: PromiseIndex): Uint8Array { const status = env.promise_result(promiseIndex as unknown as bigint, 0); assert( Number(status) === PromiseResult.Successful, `Promise result ${ status == PromiseResult.Failed ? "Failed" : status == PromiseResult.NotReady ? "NotReady" : status }` ); return env.read_register(0); } /** * Returns the result of the NEAR promise for the passed promise index as utf-8 string * * @param promiseIndex - The index of the promise to return the result for. */ export function promiseResult(promiseIndex: PromiseIndex): string { return decode(promiseResultRaw(promiseIndex)); } /** * Executes the promise in the NEAR WASM virtual machine. * * @param promiseIndex - The index of the promise to execute. */ export function promiseReturn(promiseIndex: PromiseIndex): void { env.promise_return(promiseIndex as unknown as bigint); } /** * Returns sha256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function sha256(value: Uint8Array): Uint8Array { env.sha256(value, 0); return env.read_register(0); } /** * Returns keccak256 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function keccak256(value: Uint8Array): Uint8Array { env.keccak256(value, 0); return env.read_register(0); } /** * Returns keccak512 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function keccak512(value: Uint8Array): Uint8Array { env.keccak512(value, 0); return env.read_register(0); } /** * Returns ripemd160 hash of given value * @param value - value to be hashed, in Bytes * @returns hash result in Bytes */ export function ripemd160(value: Uint8Array): Uint8Array { env.ripemd160(value, 0); return env.read_register(0); } /** * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding * signature along with v recovery byte. Takes in an additional flag to check for * malleability of the signature which is generally only ideal for transactions. * * @param hash - 32-byte message hash * @param sig - signature * @param v - number of recovery byte * @param malleabilityFlag - whether to check malleability * @returns 64 bytes representing the public key if the recovery was successful. */ export function ecrecover( hash: Uint8Array, sig: Uint8Array, v: number, malleabilityFlag: number ): Uint8Array | null { const returnValue = env.ecrecover(hash, sig, v, malleabilityFlag, 0); if (returnValue === 0n) { return null; } return env.read_register(0); } // NOTE: "env.panic(msg)" is not exported, use "throw Error(msg)" instead /** * Panic the transaction execution with given message * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence */ export function panicUtf8(msg: Uint8Array): never { env.panic_utf8(msg); } /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-8 sequence */ export function logUtf8(msg: Uint8Array) { env.log_utf8(msg); } /** * Log the message in transaction logs * @param msg - message in raw bytes, which should be a valid UTF-16 sequence */ export function logUtf16(msg: Uint8Array) { env.log_utf16(msg); } /** * Returns the number of staked NEAR of given validator, in yoctoNEAR * @param accountId - validator's AccountID * @returns - staked amount */ export function validatorStake(accountId: string): bigint { return env.validator_stake(accountId); } /** * Returns the number of staked NEAR of all validators, in yoctoNEAR * @returns total staked amount */ export function validatorTotalStake(): bigint { return env.validator_total_stake(); } /** * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i * mul_i g_{1 i} should be equal result. * * @param value - sequence of (g1:G1, fr:Fr), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * `value` is encoded as packed, little-endian * `[((u256, u256), u256)]` slice. * * @returns multi exp sum */ export function altBn128G1Multiexp(value: Uint8Array): Uint8Array { env.alt_bn128_g1_multiexp(value, 0); return env.read_register(0); } /** * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i * (-1)^{sign_i} g_{1 i} should be equal result. * * @param value - sequence of (sign:bool, g1:G1), where * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. * value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns sum over Fq. */ export function altBn128G1Sum(value: Uint8Array): Uint8Array { env.alt_bn128_g1_sum(value, 0); return env.read_register(0); } /** * Computes pairing check on alt_bn128 curve. * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing * * @param value - sequence of (g1:G1, g2:G2), where * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 * Fq2 is complex field element (re: Fq, im: Fq) * G1 is point (x:Fq, y:Fq) on alt_bn128, * alt_bn128 is Y^2 = X^3 + 3 curve over Fq * `value` is encoded a as packed, little-endian * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. * * @returns whether pairing check pass */ export function altBn128PairingCheck(value: Uint8Array): boolean { return env.alt_bn128_pairing_check(value) === 1n; } ================================================ FILE: packages/near-sdk-js/src/cli/abi.ts ================================================ import ts, { ClassDeclaration, Decorator, NodeArray } from "typescript"; import JSON5 from 'json5'; import * as abi from "near-abi"; import * as TJS from "near-typescript-json-schema"; import { JSONSchema7 } from "json-schema"; import * as fs from "fs"; import { LIB_VERSION } from "../version.js"; function parseMetadata(packageJsonPath: string): abi.AbiMetadata { const packageJson = JSON5.parse(fs.readFileSync(packageJsonPath, "utf8")); let authors: string[] = []; if (packageJson["author"]) authors.push(packageJson["author"]); authors = authors.concat(packageJson["contributors"] || []); return { name: packageJson["name"], version: packageJson["version"], authors, build: { compiler: "tsc " + ts.version, builder: "near-sdk-js " + LIB_VERSION, }, }; } function getProgramFromFiles( files: string[], jsonCompilerOptions: string, basePath = "./" ): ts.Program { const { options, errors } = ts.convertCompilerOptionsFromJson( jsonCompilerOptions, basePath ); if (errors.length > 0) { errors.forEach((error) => { console.log(error.messageText); }); throw Error("Invalid compiler options"); } return ts.createProgram(files, options); } function validateNearClass(node: ts.Node) { if (node.kind !== ts.SyntaxKind.ClassDeclaration) { throw Error("Expected NEAR function to be inside of a class"); } const classDeclaration = node as ClassDeclaration; const decorators = classDeclaration.decorators || ([] as unknown as NodeArray); const containsNearBindgen = decorators.some((decorator) => { if (decorator.expression.kind !== ts.SyntaxKind.CallExpression) return false; const decoratorExpression = decorator.expression as ts.CallExpression; if (decoratorExpression.expression.kind !== ts.SyntaxKind.Identifier) return false; const decoratorIdentifier = decoratorExpression.expression as ts.Identifier; const decoratorName = decoratorIdentifier.text; return decoratorName === "NearBindgen"; }); if (!containsNearBindgen) { throw Error( "Expected NEAR function to be inside of a class decorated with @NearBindgen" ); } } export function runAbiCompilerPlugin( tsFile: string, packageJsonPath: string, tsConfigJsonPath: string ) { const tsConfig = JSON5.parse(fs.readFileSync(tsConfigJsonPath, "utf8")); const program = getProgramFromFiles([tsFile], tsConfig["compilerOptions"]); const typeChecker = program.getTypeChecker(); const diagnostics = ts.getPreEmitDiagnostics(program); if (diagnostics.length > 0) { diagnostics.forEach((diagnostic) => { const message = ts.flattenDiagnosticMessageText( diagnostic.messageText, "\n" ); if (diagnostic.file && diagnostic.start) { const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); console.error( `${diagnostic.file.fileName} (${line + 1},${ character + 1 }): ${message}` ); } else { console.error(message); } }); throw Error("Failed to compile the contract"); } const generator = TJS.buildGenerator(program); if (!generator) { throw Error( "Failed to generate ABI due to an unexpected typescript-json-schema error. Please report this." ); } const abiFunctions: abi.AbiFunction[] = []; program.getSourceFiles().forEach((sourceFile, _sourceFileIdx) => { function inspect(node: ts.Node, tc: ts.TypeChecker) { if (node.kind === ts.SyntaxKind.MethodDeclaration) { const methodDeclaration = node as ts.MethodDeclaration; const decorators = methodDeclaration.decorators || ([] as unknown as NodeArray); let isCall = false; let isView = false; let isInit = false; const abiModifiers: abi.AbiFunctionModifier[] = []; decorators.forEach((decorator) => { if (decorator.expression.kind !== ts.SyntaxKind.CallExpression) return; const decoratorExpression = decorator.expression as ts.CallExpression; if (decoratorExpression.expression.kind !== ts.SyntaxKind.Identifier) return; const decoratorIdentifier = decoratorExpression.expression as ts.Identifier; const decoratorName = decoratorIdentifier.text; if (decoratorName === "call") { isCall = true; decoratorExpression.arguments.forEach((arg) => { if (arg.kind !== ts.SyntaxKind.ObjectLiteralExpression) return; const objLiteral = arg as ts.ObjectLiteralExpression; objLiteral.properties.forEach((prop) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const propName = (prop.name as any).text; if (propName === "privateFunction") { if (prop.kind !== ts.SyntaxKind.PropertyAssignment) return; const propAssignment = prop as ts.PropertyAssignment; const init = propAssignment.initializer; if (init.kind === ts.SyntaxKind.TrueKeyword) { abiModifiers.push(abi.AbiFunctionModifier.Private); } else if (init.kind === ts.SyntaxKind.FalseKeyword) { // Do nothing } else { throw Error( "Unexpected initializer for `privateFunction`: kind " + init.kind ); } } if (propName === "payableFunction") { if (prop.kind !== ts.SyntaxKind.PropertyAssignment) return; const propAssignment = prop as ts.PropertyAssignment; const init = propAssignment.initializer; if (init.kind === ts.SyntaxKind.TrueKeyword) { abiModifiers.push(abi.AbiFunctionModifier.Payable); } else if (init.kind === ts.SyntaxKind.FalseKeyword) { // Do nothing } else { throw Error( "Unexpected initializer for `publicFunction`: kind " + init.kind ); } } }); }); } if (decoratorName === "view") isView = true; if (decoratorName === "initialize") { isInit = true; abiModifiers.push(abi.AbiFunctionModifier.Init); } }); const nearDecoratorsCount = [isCall, isView, isInit].filter( (b) => b ).length; if (nearDecoratorsCount > 1) { throw Error( "NEAR function cannot be init, call and view at the same time" ); } if (nearDecoratorsCount === 0) { return; } validateNearClass(node.parent); let abiParams: abi.AbiJsonParameter[] = []; if (methodDeclaration.parameters.length > 1) { throw Error( "Expected NEAR function to have a single object parameter, but got " + methodDeclaration.parameters.length ); } else if (methodDeclaration.parameters.length === 1) { const jsonObjectParameter = methodDeclaration.parameters[0]; if (!jsonObjectParameter.type) { throw Error( "Expected NEAR function to have explicit types, e.g. `{ id }: {id : string }`" ); } if (jsonObjectParameter.type.kind !== ts.SyntaxKind.TypeLiteral) { throw Error( "Expected NEAR function to have a single object binding parameter, e.g. `{ id }: { id: string }`" ); } const typeLiteral = jsonObjectParameter.type as ts.TypeLiteralNode; abiParams = typeLiteral.members.map((member) => { if (member.kind !== ts.SyntaxKind.PropertySignature) { throw Error( "Expected NEAR function to have a single object binding parameter, e.g. `{ id }: { id: string }`" ); } const propertySignature = member as ts.PropertySignature; const nodeType = tc.getTypeAtLocation(propertySignature.type); const schema = generator.getTypeDefinition(nodeType, true); const abiParameter: abi.AbiJsonParameter = { // eslint-disable-next-line @typescript-eslint/no-explicit-any name: (propertySignature.name as any).text, type_schema: schema as JSONSchema7, }; return abiParameter; }); } let abiResult: abi.AbiType | undefined = undefined; const returnType = methodDeclaration.type; if (returnType) { const nodeType = tc.getTypeAtLocation(returnType); const schema = generator.getTypeDefinition(nodeType, true); abiResult = { serialization_type: abi.AbiSerializationType.Json, type_schema: schema, }; } const abiFunction: abi.AbiFunction = { // eslint-disable-next-line @typescript-eslint/no-explicit-any name: (methodDeclaration.name as any).text, kind: isView ? abi.AbiFunctionKind.View : abi.AbiFunctionKind.Call, }; if (abiModifiers.length > 0) { abiFunction.modifiers = abiModifiers; } if (abiParams.length > 0) { abiFunction.params = { serialization_type: abi.AbiSerializationType.Json, args: abiParams, }; } if (abiResult) { abiFunction.result = abiResult; } abiFunctions.push(abiFunction); } else { ts.forEachChild(node, (n) => inspect(n, tc)); } } inspect(sourceFile, typeChecker); }); const abiRoot: abi.AbiRoot = { schema_version: abi.SCHEMA_VERSION, metadata: parseMetadata(packageJsonPath), body: { functions: abiFunctions, root_schema: generator.getSchemaForSymbol( "String", true, false ) as JSONSchema7, }, }; return abiRoot; } ================================================ FILE: packages/near-sdk-js/src/cli/build-tools/include-bytes.ts ================================================ import { PluginPass } from "@babel/core"; import { Node, Visitor } from "@babel/traverse"; import * as t from "@babel/types"; import { readFileSync } from "fs"; import { join, dirname } from "path"; const assertStringLiteral: typeof t["assertStringLiteral"] = t.assertStringLiteral; export default function (): { visitor: Visitor } { return { visitor: { CallExpression( path, { opts, file }: PluginPass & { opts: { root?: string } } ): void { if (!("name" in path.node.callee)) { return; } // Extract the called method name. const name = path.node.callee.name; // If the method name is not "includeBytes" do nothing. if (name === "includeBytes") { // Extract the called method arguments. const args = path.node.arguments; // Get the path of file const filename = file.opts.filename; // User settings const root = opts.root || dirname(filename); // Read binary file into bytes, so encoding is 'latin1' (each byte is 0-255, become one character) const encoding = "latin1"; const [firstArg] = args; // Require first arg to be a string literal assertStringLiteral(firstArg); // Error if filename is not found if (filename === undefined || filename === "unknown") { throw new Error("`includeBytes` function called outside of file"); } if (!("value" in firstArg && typeof firstArg.value === "string")) { throw new Error( `\`includeBytes\` function called with invalid argument: ${args[0]}` ); } // Generate and locate the file const fileRelPath = firstArg.value; // Get literal string value const filePath = join(root, fileRelPath); const fileSrc = readFileSync(filePath, { encoding }).toString(); path.replaceWith( t.callExpression( t.memberExpression( t.identifier("env"), t.identifier("latin1_string_to_uint8array") ), [t.stringLiteral(fileSrc)] ) as Node ); } }, }, }; } ================================================ FILE: packages/near-sdk-js/src/cli/build-tools/near-bindgen-exporter.ts ================================================ import { PluginPass } from "@babel/core"; import { Node, Visitor } from "@babel/traverse"; import * as t from "@babel/types"; import signal from "signale"; const { Signale } = signal; /** * A list of supported method types/decorators. */ const methodTypes = ["call", "view", "initialize", "migrate"]; /** * A helper function that inserts a new throw Error statement with * the passed message. * * @param message - The message to throw inside the error */ function throwError(message: string): t.BlockStatement { return t.blockStatement([ t.throwStatement( t.newExpression(t.identifier("Error"), [t.stringLiteral(message)]) ), ]); } /** * A helper function that inserts a new state reading expression. * It reads state into _\_state_ via _\_getState_. * * ```typescript * const _state = Contract._getState(); * ``` * * @param classId - The class ID of the class which we are extending. */ function readState(classId: t.Identifier, methodType: string): t.VariableDeclaration { if (methodType === "migrate") { return t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("_state"), t.nullLiteral() ), ]); } return t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("_state"), t.callExpression( t.memberExpression(classId, t.identifier("_getState")), [] ) ), ]); } /** * A helper function that inserts a double initialization check. * * ```typescript * if (_state) { * throw new Error('Contract already initialized'); * } * ``` * * @param methodType - The type of the method being called. */ function preventDoubleInit( methodType: string ): t.EmptyStatement | t.IfStatement { if (methodType !== "initialize") { return t.emptyStatement(); } return t.ifStatement( t.identifier("_state"), throwError("Contract already initialized") ); } /** * A helper function that inserts a initialization check. * * ```typescript * if (!_state) { * throw new Error('Contract must be initialized'); * } * ``` * * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. * * @returns {t.EmptyStatement | t.IfStatement} */ function ensureInitBeforeCall( classId: t.Identifier, methodType: string ): t.EmptyStatement | t.IfStatement { if (!["call", "view"].includes(methodType)) { return t.emptyStatement(); } return t.ifStatement( t.logicalExpression( "&&", t.unaryExpression("!", t.identifier("_state")), t.callExpression( t.memberExpression(classId, t.identifier("_requireInit")), [] ) ), throwError("Contract must be initialized") ); } /** * A helper function that inserts a contract creation expression. * It creates a new instance of the class by calling the _\_create_ method * on the contract class. * * ```typescript * let _contract = Contract._create(); * ``` * * @param classId - The class ID of the class being extended. */ function initializeContractClass(classId: t.Identifier): t.VariableDeclaration { return t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("_contract"), t.callExpression(t.memberExpression(classId, t.identifier("_create")), []) ), ]); } /** * A helper function that inserts a state reconstruction statement. * It calls the _\_reconstruct_ method on the _\_contract_ object. * * ```typescript * if (_state) { * Contract._reconstruct(_contract, _state); * } * ``` * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. */ function reconstructState( classId: t.Identifier, methodType: string ): t.EmptyStatement | t.IfStatement { if (!["call", "view"].includes(methodType)) { return t.emptyStatement(); } return t.ifStatement( t.identifier("_state"), t.blockStatement([ t.expressionStatement( t.callExpression( t.memberExpression(classId, t.identifier("_reconstruct")), [t.identifier("_contract"), t.identifier("_state")] ) ), ]) ); } /** * A helper function that inserts a argument collection expression. * It calls the _\_getArgs_ function on the class object. * * ```typescript * const _args = Contract._getArgs(); * ``` * @param classId - The class ID of the class being extended. */ function collectArguments(classId: t.Identifier): t.VariableDeclaration { return t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("_args"), t.callExpression( t.memberExpression(classId, t.identifier("_getArgs")), [] ) ), ]); } /** * A helper function that inserts a contract method call expression. * It calls the appropriate contract method and passes the collected _\_args_. * * ```typescript * const _result = _contract.method(args); * ``` * * @param methodName - The name of the method being called. */ function callContractMethod(methodName: string): t.VariableDeclaration { return t.variableDeclaration("const", [ t.variableDeclarator( t.identifier("_result"), t.callExpression( t.memberExpression(t.identifier("_contract"), t.identifier(methodName)), [t.identifier("_args")] ) ), ]); } /** * A helper function that inserts a save to storage expression. * It calls the _\_saveToStorage_ method if a initialize or call method is called. * * ```typescript * Contract._saveToStorage(_contract); * ``` * * @param classId - The class ID of the class being extended. * @param methodType - The type of the method being called. */ function saveToStorage( classId: t.Identifier, methodType: string ): t.EmptyStatement | t.ExpressionStatement { if (!["initialize", "call", "migrate"].includes(methodType)) { return t.emptyStatement(); } return t.expressionStatement( t.callExpression( t.memberExpression(classId, t.identifier("_saveToStorage")), [t.identifier("_contract")] ) ); } /** * A helper function that inserts a NearPromise execution call or a valuer return call. * It checks for the return type of the called function and either performs a NearPromise * _onReturn_ call or a _value\_return_ environment function to return the value to the callee. * * ```typescript * if (_result !== undefined) { * if (_result && _result.constructor && _result.constructor.name === 'NearPromise') { * _result.onReturn(); * } else { * near.valueReturnRaw(_contract._serialize(result)); * } * } * ``` * * @param classId - The class ID of the class being extended. */ function executePromise(classId: t.Identifier): t.IfStatement { return t.ifStatement( t.binaryExpression( "!==", t.identifier("_result"), t.identifier("undefined") ), t.ifStatement( t.logicalExpression( "&&", t.logicalExpression( "&&", t.identifier("_result"), t.memberExpression( t.identifier("_result"), t.identifier("constructor") ) ), t.binaryExpression( "===", t.memberExpression( t.memberExpression( t.identifier("_result"), t.identifier("constructor") ), t.identifier("name") ), t.stringLiteral("NearPromise") ) ), t.expressionStatement( t.callExpression( t.memberExpression(t.identifier("_result"), t.identifier("onReturn")), [] ) ), t.expressionStatement( t.callExpression( t.memberExpression(t.identifier("env"), t.identifier("value_return")), [ t.callExpression( t.memberExpression(classId, t.identifier("_serialize")), [t.identifier("_result"), t.booleanLiteral(true)] ), ] ) ) ) ); } /** * A helper function that inserts the overridden function declaration into the class. * * @param classId - The class ID of the class being extended. * @param methodName - The name of the method being called. * @param methodType - The type of the method being called. */ function createDeclaration( classId: t.Identifier, methodName: string, methodType: string ): t.ExportNamedDeclaration { return t.exportNamedDeclaration( t.functionDeclaration( t.identifier(methodName), [], t.blockStatement([ // Read the state of the contract from storage. // const _state = Contract._getState(); readState(classId, methodType), // Throw if initialized on any subsequent init function calls. // if (_state) { throw new Error('Contract already initialized'); } preventDoubleInit(methodType), // Throw if NOT initialized on any non init function calls. // if (!_state) { throw new Error('Contract must be initialized'); } ensureInitBeforeCall(classId, methodType), // Create instance of contract by calling _create function. // let _contract = Contract._create(); initializeContractClass(classId), // Reconstruct the contract with the state if the state is valid. // if (_state) { Contract._reconstruct(_contract, _state); } reconstructState(classId, methodType), // Collect the arguments sent to the function. // const _args = Contract._getArgs(); collectArguments(classId), // Perform the actual function call to the appropriate contract method. // const _result = _contract.method(args); callContractMethod(methodName), // If the method called is either an initialize or call method type, save the changes to storage. // Contract._saveToStorage(_contract); saveToStorage(classId, methodType), // If a NearPromise is returned from the function call the onReturn method to execute the promise. // if (_result !== undefined) // if (_result && _result.constructor && _result.constructor.name === 'NearPromise') // _result.onReturn(); // else // near.valueReturnRaw(_contract._serialize(result)); executePromise(classId), ]) ) ); } export default function (): { visitor: Visitor } { return { visitor: { ClassDeclaration( path, { opts: { verbose } }: PluginPass & { opts: { verbose: boolean } } ): void { // Capture the node of the current path. const classNode = path.node; // Check that the class is decorated with NearBindgen otherwise do nothing. if ( classNode.decorators && "callee" in classNode.decorators[0].expression && "name" in classNode.decorators[0].expression.callee && classNode.decorators[0].expression.callee.name === "NearBindgen" ) { // Iterate over the children of the class node. classNode.body.body.forEach((child) => { // Check that the child is a class method and has decorators. if ( child.type === "ClassMethod" && child.kind === "method" && child.decorators && "callee" in child.decorators[0].expression && "name" in child.decorators[0].expression.callee ) { // Capture the decorator name. const methodType = child.decorators[0].expression.callee.name; // Check that the decorator is one of the supported method types. if (methodTypes.includes(methodType) && "name" in child.key) { // Insert the method override into the class declaration. path.insertAfter( createDeclaration( classNode.id as t.Identifier, child.key.name, methodType ) as Node ); if (verbose) { new Signale({ scope: "near-bindgen-exporter", }).info(`Babel ${child.key.name} method export done.`); } } } }); } }, }, }; } ================================================ FILE: packages/near-sdk-js/src/cli/cli.ts ================================================ #!/usr/bin/env node import fs from "fs"; import path, { basename, dirname } from "path"; import { nodeResolve } from "@rollup/plugin-node-resolve"; import sourcemaps from "rollup-plugin-sourcemaps"; import { babel } from "@rollup/plugin-babel"; import { rollup } from "rollup"; import { Command } from "commander"; import signal from "signale"; import { executeCommand, validateContract } from "./utils.js"; import { runAbiCompilerPlugin } from "./abi.js"; const { Signale } = signal; const PROJECT_DIR = process.cwd(); const NEAR_SDK_JS = "node_modules/near-sdk-js"; const TSC = "node_modules/.bin/tsc"; const QJSC_DIR = `${NEAR_SDK_JS}/lib/cli/deps/quickjs`; const QJSC = `${NEAR_SDK_JS}/lib/cli/deps/qjsc`; const program = new Command(); program .name("near-sdk-js") .addCommand( new Command("build") .usage("[source] [target]") .description("Build NEAR JS Smart-contract") .argument("[source]", "Contract to build.", "src/index.js") .argument("[target]", "Target file path and name.", "build/contract.wasm") .argument("[packageJson]", "Target file path and name.", "package.json") .argument("[tsConfig]", "Target file path and name.", "tsconfig.json") .option("--verbose", "Whether to print more verbose output.", false) .option("--generateABI", "Whether to generate ABI.", false) .action(buildCom) ) .addCommand( new Command("validateContract") .usage("[source]") .description( "Validate a NEAR JS Smart-contract. Validates the contract by checking that all parameters are initialized in the constructor. Works only for typescript." ) .argument("[source]", "Contract to validate.", "src/index.ts") .option("--verbose", "Whether to print more verbose output.", false) .action(validateCom) ) .addCommand( new Command("checkTypescript") .usage("[source]") .description( "Run TSC with some cli flags - warning - ignores tsconfig.json." ) .argument("[source]", "Typescript file to validate", "src/index.ts") .option("--verbose", "Whether to print more verbose output.", false) .action(checkTypescriptCom) ) .addCommand( new Command("createJsFileWithRollup") .usage("[source] [target]") .description( "Create intermediate javascript file for later processing with QJSC" ) .argument("[source]", "Contract to build.", "src/index.js") .argument( "[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm" ) .option("--verbose", "Whether to print more verbose output.", false) .action(createJsFileWithRollupCom) ) .addCommand( new Command("transpileJsAndBuildWasm") .usage("[source] [target]") .description( "Transpiles the target javascript file into .c and .h using QJSC then compiles that into wasm using clang" ) .argument( "[target]", "Target file path and name. The js file must correspond to the same path with the js extension.", "build/contract.wasm" ) .option("--verbose", "Whether to print more verbose output.", false) .action(transpileJsAndBuildWasmCom) ) .parse(); function getTargetDir(target: string): string { return dirname(target); } function getTargetExt(target: string): string { return target.split(".").pop(); } function getTargetFileName(target: string): string { return basename(target, `.${getTargetExt(target)}`); } function getRollupTarget(target: string): string { return `${getTargetDir(target)}/${getTargetFileName(target)}.js`; } function getQjscTarget(target: string): string { return `${getTargetDir(target)}/${getTargetFileName(target)}.h`; } function getContractTarget(target: string): string { return `${getTargetDir(target)}/${getTargetFileName(target)}.wasm`; } function getContractAbi(target: string): string { return `${getTargetDir(target)}/${getTargetFileName(target)}-abi.json`; } function requireTargetExt(target: string): void { if (getTargetExt(target) === "wasm") { return; } signal.error( `Unsupported target ${getTargetExt( target )}, make sure target ends with .wasm!` ); process.exit(1); } function ensureTargetDirExists(target: string): void { const targetDir = getTargetDir(target); if (fs.existsSync(targetDir)) { return; } signal.await(`Creating ${targetDir} directory...`); fs.mkdirSync(targetDir, {}); } export async function validateCom( source: string, { verbose = false }: { verbose: boolean } ): Promise { const signale = new Signale({ scope: "validate", interactive: !verbose }); signale.await(`Validating ${source} contract...`); if (!(await validateContract(source, verbose))) { process.exit(1); } } export async function checkTypescriptCom( source: string, { verbose = false }: { verbose: boolean } ): Promise { const signale = new Signale({ scope: "checkTypescript", interactive: !verbose, }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { signale.info(`Source file is not a typescript file ${source}`); return; } signale.await(`Typechecking ${source} with tsc...`); await checkTsBuildWithTsc(source, verbose); } export async function generateAbi( source: string, target: string, packageJson: string, tsConfig: string, { verbose = false }: { verbose: boolean } ): Promise { const signale = new Signale({ scope: "generateAbi", interactive: !verbose }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { signale.info( `Skipping ABI generation as source file is not a typescript file ${source}` ); return; } signale.await("Generating ABI..."); const abi = runAbiCompilerPlugin(source, packageJson, tsConfig); fs.writeFileSync(getContractAbi(target), JSON.stringify(abi, null, 2)); signale.success(`Generated ${getContractAbi(target)} ABI successfully!`); } export async function createJsFileWithRollupCom( source: string, target: string, { verbose = false }: { verbose: boolean } ): Promise { const signale = new Signale({ scope: "createJsFileWithRollup", interactive: !verbose, }); requireTargetExt(target); ensureTargetDirExists(target); signale.await(`Creating ${source} file with Rollup...`); await createJsFileWithRullup(source, getRollupTarget(target), verbose); } export async function transpileJsAndBuildWasmCom( target: string, { verbose = false }: { verbose: boolean } ): Promise { const signale = new Signale({ scope: "transpileJsAndBuildWasm", interactive: !verbose, }); requireTargetExt(target); ensureTargetDirExists(target); signale.await(`Creating ${getQjscTarget(target)} file with QJSC...`); await createHeaderFileWithQjsc( getRollupTarget(target), getQjscTarget(target), verbose ); signale.await("Generating methods.h file..."); await createMethodsHeaderFile(getRollupTarget(target), verbose); signale.await(`Creating ${getContractTarget(target)} contract...`); await createWasmContract( getQjscTarget(target), getContractTarget(target), verbose ); signale.await("Executing wasi-stub..."); await wasiStubContract(getContractTarget(target), verbose); signale.success( `Generated ${getContractTarget(target)} contract successfully!` ); } export async function buildCom( source: string, target: string, packageJson: string, tsConfig: string, { verbose = false, generateABI = false }: { verbose: boolean, generateABI: boolean }, ): Promise { const signale = new Signale({ scope: "build", interactive: !verbose }); requireTargetExt(target); signale.await(`Building ${source} contract...`); await checkTypescriptCom(source, { verbose }); ensureTargetDirExists(target); if (generateABI) { await generateAbi(source, target, packageJson, tsConfig, { verbose }); } await validateCom(source, { verbose }); await createJsFileWithRollupCom(source, target, { verbose }); await transpileJsAndBuildWasmCom(target, { verbose }); } async function checkTsBuildWithTsc( sourceFileWithPath: string, verbose = false ) { await executeCommand( `${TSC} --noEmit --skipLibCheck --experimentalDecorators --target es2020 --moduleResolution node ${sourceFileWithPath}`, verbose ); } // Common build function async function createJsFileWithRullup( sourceFileWithPath: string, rollupTarget: string, verbose = false ) { const bundle = await rollup({ input: sourceFileWithPath, plugins: [ nodeResolve({ extensions: [".js", ".ts"], }), sourcemaps(), // commonjs(), babel({ babelHelpers: "bundled", extensions: [".ts", ".js", ".jsx", ".es6", ".es", ".mjs"], presets: ["@babel/preset-typescript"], plugins: [ "near-sdk-js/lib/cli/build-tools/include-bytes.js", [ "near-sdk-js/lib/cli/build-tools/near-bindgen-exporter.js", { verbose }, ], ["@babel/plugin-proposal-decorators", { version: "legacy" }], ], }), ], }); await bundle.write({ sourcemap: true, file: rollupTarget, format: "es", }); } async function createHeaderFileWithQjsc( rollupTarget: string, qjscTarget: string, verbose = false ) { await executeCommand( `${QJSC} -c -m -o ${qjscTarget} -N code ${rollupTarget}`, verbose ); } async function createMethodsHeaderFile(rollupTarget: string, verbose = false) { const buildPath = path.dirname(rollupTarget); if (verbose) { new Signale({ scope: "method-header" }).info(rollupTarget); } const mod = await import(`${PROJECT_DIR}/${rollupTarget}`); const exportNames = Object.keys(mod); if (exportNames.includes('panic')) { signal.error( "'panic' is a reserved word, please use another name for contract method" ); process.exit(1); } const methods = exportNames.reduce( (result, key) => `${result}DEFINE_NEAR_METHOD(${key})\n`, "" ); fs.writeFileSync(`${buildPath}/methods.h`, methods); } async function createWasmContract( qjscTarget: string, contractTarget: string, verbose = false ) { const WASI_SDK_PATH = `${NEAR_SDK_JS}/lib/cli/deps/wasi-sdk`; const CC = `${WASI_SDK_PATH}/bin/clang --sysroot=${WASI_SDK_PATH}/share/wasi-sysroot`; const DEFS = `-D_GNU_SOURCE '-DCONFIG_VERSION="2021-03-27"' -DCONFIG_BIGNUM`; const INCLUDES = `-I${QJSC_DIR} -I.`; const ORIGINAL_BUILDER_PATH = `${NEAR_SDK_JS}/builder/builder.c`; const NEW_BUILDER_PATH = `${path.dirname(contractTarget)}/builder.c`; const SOURCES = `${NEW_BUILDER_PATH} ${QJSC_DIR}/quickjs.c ${QJSC_DIR}/libregexp.c ${QJSC_DIR}/libunicode.c ${QJSC_DIR}/cutils.c ${QJSC_DIR}/quickjs-libc-min.c ${QJSC_DIR}/libbf.c`; const LIBS = `-lm`; // copying builder.c file to the build folder fs.cpSync(ORIGINAL_BUILDER_PATH, NEW_BUILDER_PATH); fs.renameSync(qjscTarget, "build/code.h"); await executeCommand( `${CC} --target=wasm32-wasi -nostartfiles -Oz -flto ${DEFS} ${INCLUDES} ${SOURCES} ${LIBS} -Wl,--no-entry -Wl,--allow-undefined -Wl,-z,stack-size=${ 256 * 1024 } -Wl,--lto-O3 -o ${contractTarget}`, verbose ); } async function wasiStubContract(contractTarget: string, verbose = false) { const WASI_STUB = `${NEAR_SDK_JS}/lib/cli/deps/binaryen/wasi-stub/run.sh`; await executeCommand(`${WASI_STUB} ${contractTarget}`, verbose); } ================================================ FILE: packages/near-sdk-js/src/cli/post-install.ts ================================================ import { executeCommand, download } from "./utils.js"; import signal from "signale"; import os from "os"; import fs from "fs"; const { Signale } = signal; const signale = new Signale({ scope: "postinstall", interactive: true }); // Clean existing deps folder process.chdir("lib/cli"); const DEPS = "deps"; fs.rmSync(DEPS, { recursive: true, force: true }); fs.mkdirSync(DEPS); process.chdir(DEPS); const PLATFORM = os.platform(); const ARCH = os.arch(); console.log(`Current platform: ${PLATFORM}, current architecture: ${ARCH}`); const SUPPORTED_PLATFORMS = ["linux", "darwin"]; // Unsupported platforms: 'win32', 'aix', 'freebsd', 'openbsd', 'sunos', 'android' const SUPPORTED_ARCH = ["x64", "arm64"]; // Unsupported arch: 'arm', 'ia32', 'mips','mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x32' if (!SUPPORTED_PLATFORMS.includes(PLATFORM)) { console.error(`Platform ${PLATFORM} is not supported at the moment`); process.exit(1); } if (!SUPPORTED_ARCH.includes(ARCH)) { console.error(`Architecture ${ARCH} is not supported at the moment`); process.exit(1); } signale.await("Installing wasi-stub..."); const BINARYEN_VERSION = `0.1.16`; const BINARYEN_VERSION_TAG = `v${BINARYEN_VERSION}`; const BINARYEN_SYSTEM_NAME = PLATFORM === "linux" ? "Linux" : PLATFORM === "darwin" ? "macOS" : PLATFORM === "win32" ? "windows" : "other"; const BINARYEN_ARCH_NAME = (ARCH == 'aarch64') ? 'ARM64' : ARCH.toUpperCase(); const BINARYEN_TAR_NAME = `binaryen-${BINARYEN_SYSTEM_NAME}-${BINARYEN_ARCH_NAME}.tar.gz`; await download( `https://github.com/ailisp/binaryen/releases/download/${BINARYEN_VERSION_TAG}/${BINARYEN_TAR_NAME}` ); fs.mkdirSync("binaryen"); await executeCommand(`tar xvf ${BINARYEN_TAR_NAME} --directory binaryen`); fs.rmSync(BINARYEN_TAR_NAME); signale.await("Installing QuickJS..."); const QUICK_JS_VERSION = `0.1.3`; const QUICK_JS_VERSION_TAG = `v${QUICK_JS_VERSION}`; const QUICK_JS_SYSTEM_NAME = PLATFORM === "linux" ? "Linux" : PLATFORM === "darwin" ? "macOS" : PLATFORM === "win32" ? "windows" : "other"; const QUICK_JS_ARCH_NAME = ARCH === "x64" ? "X64" : ARCH === "arm64" ? "arm64" : "other"; const QUICK_JS_TAR_NAME = `${QUICK_JS_VERSION_TAG}.tar.gz`; const QUICK_JS_DOWNLOADED_FOLDER_NAME = `quickjs-${QUICK_JS_VERSION}`; const QUICK_JS_TARGET_FOLDER_NAME = "quickjs"; const QUICK_JS_DOWNLOADED_NAME = `qjsc-${QUICK_JS_SYSTEM_NAME}-${QUICK_JS_ARCH_NAME}`; const QUICK_JS_TARGET_NAME = "qjsc"; // Download QuickJS await download( `https://github.com/near/quickjs/releases/download/${QUICK_JS_VERSION_TAG}/qjsc-${QUICK_JS_SYSTEM_NAME}-${QUICK_JS_ARCH_NAME}` ); await download( `https://github.com/near/quickjs/archive/refs/tags/${QUICK_JS_VERSION_TAG}.tar.gz` ); // Extract QuickJS await executeCommand(`tar xvf ${QUICK_JS_TAR_NAME}`); // Delete .tar file fs.rmSync(QUICK_JS_TAR_NAME); // Delete version from folder name fs.renameSync(QUICK_JS_DOWNLOADED_FOLDER_NAME, QUICK_JS_TARGET_FOLDER_NAME); // Rename qjsc file fs.renameSync(QUICK_JS_DOWNLOADED_NAME, QUICK_JS_TARGET_NAME); // chmod qjsc fs.chmodSync(QUICK_JS_TARGET_NAME, 0o755); signale.await("Installing wasi-sdk..."); const WASI_SDK_MAJOR_VER = 11; const WASI_SDK_MINOR_VER = 0; const WASI_SDK_DOWNLOADED_FOLDER_NAME = `wasi-sdk-${WASI_SDK_MAJOR_VER}.${WASI_SDK_MINOR_VER}`; const WASI_SDK_SYSTEM_NAME = PLATFORM === "linux" ? "linux" : PLATFORM === "darwin" ? "macos" : PLATFORM === "win32" ? "windows" : "other"; const WASI_SDK_TAR_NAME = `${WASI_SDK_DOWNLOADED_FOLDER_NAME}-${WASI_SDK_SYSTEM_NAME}.tar.gz`; // Download WASI SDK await download( `https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${WASI_SDK_MAJOR_VER}/${WASI_SDK_TAR_NAME}` ); // Extract WASI SDK await executeCommand(`tar xvf ${WASI_SDK_TAR_NAME}`); // Delete .tar file fs.rmSync(WASI_SDK_TAR_NAME); // Delete version from folder name fs.renameSync(WASI_SDK_DOWNLOADED_FOLDER_NAME, "wasi-sdk"); signale.success("Successfully finished postinstall script!"); ================================================ FILE: packages/near-sdk-js/src/cli/utils.ts ================================================ import childProcess from "child_process"; import { promisify } from "util"; import signal from "signale"; import { Project } from "ts-morph"; import chalk from "chalk"; const { Signale } = signal; const exec = promisify(childProcess.exec); export async function executeCommand( command: string, verbose = false ): Promise { const signale = new Signale({ scope: "exec", interactive: !verbose }); if (verbose) { signale.info(`Running command: ${command}`); } let stdout, stderr, code = 0; try { ({ stdout, stderr } = await exec(command)); } catch (error) { ({ stdout, stderr, code } = error); } if (code != 0) { signale.error(`Command failed: ${command}`); const failDueToNameConflict = stderr.match(/conflicting types for '([a-zA-Z0-9_]+)'/); if (failDueToNameConflict && failDueToNameConflict.length > 1) { signale.error(`'${failDueToNameConflict[1]}' is a reserved word, please use another name for contract method"`); } } if (stderr && verbose) { signale.error(`Command stderr: ${stderr}`); } if (verbose) { signale.info(`Command stdout: ${stdout}`); } if (code != 0) { process.exit(1); } return stdout.trim(); } export async function download(url: string, verbose = false) { await executeCommand(`curl -LOf ${url}`, verbose); } const UNINITIALIZED_PARAMETERS_ERROR = "All parameters must be initialized in the constructor. Uninitialized parameters:"; /** * Validates the contract by checking that all parameters are initialized in the constructor. Works only for contracts written in TypeScript. * * @param contractPath - Path to the contract. * @param verbose - Whether to print verbose output. **/ export async function validateContract( contractPath: string, verbose = false ): Promise { const signale = new Signale({ scope: "validate-contract" }); const project = new Project(); project.addSourceFilesAtPaths(contractPath); const sourceFile = project.getSourceFile(contractPath); const classDeclarations = sourceFile.getClasses(); for (const classDeclaration of classDeclarations) { const classStructure = classDeclaration.getStructure(); const { decorators, properties, name } = classStructure; const hasNearBindgen = decorators.some( ({ name }) => name === "NearBindgen" ); if (hasNearBindgen) { if (verbose) { signale.info(`Validating ${name} class...`); } const constructors = classDeclaration.getConstructors(); const hasConstructor = constructors.length > 0; const propertiesToBeInited = properties.filter( ({ initializer }) => !initializer ); if (!hasConstructor && propertiesToBeInited.length === 0) { return true; } if (!hasConstructor && propertiesToBeInited.length > 0) { signale.error( chalk.redBright( `${UNINITIALIZED_PARAMETERS_ERROR} ${propertiesToBeInited .map(({ name }) => name) .join(", ")}` ) ); return false; } const [constructor] = constructors; const constructorContent = constructor.getText(); if (verbose) { signale.info("Checking for non initialized properties..."); } const nonInitedProperties = propertiesToBeInited.reduce( (properties, { name }) => { if (constructorContent.includes(`this.${name}`)) { return properties; } return [...properties, name]; }, [] as string[] ); if (nonInitedProperties.length > 0) { signale.error( chalk.redBright( `${UNINITIALIZED_PARAMETERS_ERROR} ${nonInitedProperties.join( ", " )}` ) ); return false; } } } return true; } ================================================ FILE: packages/near-sdk-js/src/collections/index.ts ================================================ export * from "./lookup-map"; export * from "./lookup-set"; export * from "./unordered-map"; export * from "./unordered-set"; export * from "./vector"; export * from "./subtype"; ================================================ FILE: packages/near-sdk-js/src/collections/lookup-map.ts ================================================ import * as near from "../api"; import { GetOptions } from "../types/collections"; import { getValueWithOptions, serializeValueWithOptions, encode, } from "../utils"; import { SubType } from "./subtype"; /** * A lookup map that stores data in NEAR storage. */ export class LookupMap extends SubType { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(readonly keyPrefix: string) { super(); } /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. */ containsKey(key: string): boolean { const storageKey = this.keyPrefix + key; return near.storageHasKey(storageKey); } /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get( key: string, options?: Omit, "serializer"> ): DataType | null { const storageKey = this.keyPrefix + key; const value = near.storageReadRaw(encode(storageKey)); if (options == undefined) { options = {}; } options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove( key: string, options?: Omit, "serializer"> ): DataType | null { const storageKey = this.keyPrefix + key; if (!near.storageRemove(storageKey)) { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param newValue - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set( key: string, newValue: DataType, options?: GetOptions ): DataType | null { const storageKey = this.keyPrefix + key; const storageValue = serializeValueWithOptions(newValue, options); if (!near.storageWriteRaw(encode(storageKey), storageValue)) { return options?.defaultValue ?? null; } const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. * @param options - Options for storing the data. */ extend( keyValuePairs: [string, DataType][], options?: GetOptions ): void { for (const [key, value] of keyValuePairs) { this.set(key, value, options); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: LookupMap): LookupMap { return new LookupMap(data.keyPrefix); } } ================================================ FILE: packages/near-sdk-js/src/collections/lookup-set.ts ================================================ import * as near from "../api"; import { GetOptions } from "../types/collections"; import { serializeValueWithOptions } from "../utils"; /** * A lookup set collection that stores entries in NEAR storage. */ export class LookupSet { /** * @param keyPrefix - The byte prefix to use when storing elements inside this collection. */ constructor(readonly keyPrefix: string) {} /** * Checks whether the collection contains the value. * * @param key - The value for which to check the presence. * @param options - Options for storing data. */ contains( key: DataType, options?: Pick, "serializer"> ): boolean { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return near.storageHasKey(storageKey); } /** * Returns true if the element was present in the set. * * @param key - The entry to remove. * @param options - Options for storing data. */ remove( key: DataType, options?: Pick, "serializer"> ): boolean { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return near.storageRemove(storageKey); } /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param key - The value to store in the collection. * @param options - Options for storing the data. */ set( key: DataType, options?: Pick, "serializer"> ): boolean { const storageKey = this.keyPrefix + serializeValueWithOptions(key, options); return !near.storageWrite(storageKey, ""); } /** * Extends the current collection with the passed in array of elements. * * @param keys - The elements to extend the collection with. * @param options - Options for storing the data. */ extend( keys: DataType[], options?: Pick, "serializer"> ): void { keys.forEach((key) => this.set(key, options)); } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: LookupSet): LookupSet { return new LookupSet(data.keyPrefix); } } ================================================ FILE: packages/near-sdk-js/src/collections/subtype.ts ================================================ import { GetOptions } from "../types/collections"; export abstract class SubType { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ subtype(): any {} set_reconstructor( options?: Omit, "serializer"> ): Omit, "serializer"> { if (options == undefined) { options = {}; } const subtype = this.subtype(); if (options.reconstructor == undefined && subtype != undefined) { if ( // eslint-disable-next-line no-prototype-builtins subtype.hasOwnProperty("class") && typeof subtype.class.reconstruct === "function" ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore options.reconstructor = subtype.class.reconstruct; } else if (typeof subtype.reconstruct === "function") { options.reconstructor = subtype.reconstruct; } } return options; } } ================================================ FILE: packages/near-sdk-js/src/collections/unordered-map.ts ================================================ import { assert, ERR_INCONSISTENT_STATE, getValueWithOptions, Mutable, serializeValueWithOptions, encode, decode, } from "../utils"; import { Vector, VectorIterator } from "./vector"; import { LookupMap } from "./lookup-map"; import { GetOptions } from "../types/collections"; import { SubType } from "./subtype"; type ValueAndIndex = [value: string, index: number]; /** * An unordered map that stores data in NEAR storage. */ export class UnorderedMap extends SubType { readonly _keys: Vector; readonly values: LookupMap; /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(readonly prefix: string) { super(); this._keys = new Vector(`${prefix}u`); // intentional different prefix with old UnorderedMap this.values = new LookupMap(`${prefix}m`); } /** * The number of elements stored in the collection. */ get length() { return this._keys.length; } /** * Checks whether the collection is empty. */ isEmpty(): boolean { return this._keys.isEmpty(); } /** * Get the data stored at the provided key. * * @param key - The key at which to look for the data. * @param options - Options for retrieving the data. */ get( key: string, options?: Omit, "serializer"> ): DataType | null { const valueAndIndex = this.values.get(key); if (valueAndIndex === null) { return options?.defaultValue ?? null; } options = this.set_reconstructor(options); const [value] = valueAndIndex; return getValueWithOptions(this.subtype(), encode(value), options); } /** * Store a new value at the provided key. * * @param key - The key at which to store in the collection. * @param value - The value to store in the collection. * @param options - Options for retrieving and storing the data. */ set( key: string, value: DataType, options?: GetOptions ): DataType | null { const valueAndIndex = this.values.get(key); const serialized = serializeValueWithOptions(value, options); if (valueAndIndex === null) { const newElementIndex = this.length; this._keys.push(key); this.values.set(key, [decode(serialized), newElementIndex]); return null; } const [oldValue, oldIndex] = valueAndIndex; this.values.set(key, [decode(serialized), oldIndex]); return getValueWithOptions(this.subtype(), encode(oldValue), options); } /** * Removes and retrieves the element with the provided key. * * @param key - The key at which to remove data. * @param options - Options for retrieving the data. */ remove( key: string, options?: Omit, "serializer"> ): DataType | null { const oldValueAndIndex = this.values.remove(key); if (oldValueAndIndex === null) { return options?.defaultValue ?? null; } const [value, index] = oldValueAndIndex; assert(this._keys.swapRemove(index) !== null, ERR_INCONSISTENT_STATE); // the last key is swapped to key[index], the corresponding [value, index] need update if (!this._keys.isEmpty() && index !== this._keys.length) { // if there is still elements and it was not the last element const swappedKey = this._keys.get(index); const swappedValueAndIndex = this.values.get(swappedKey); assert(swappedValueAndIndex !== null, ERR_INCONSISTENT_STATE); this.values.set(swappedKey, [swappedValueAndIndex[0], index]); } return getValueWithOptions(this.subtype(), encode(value), options); } /** * Remove all of the elements stored within the collection. */ clear(): void { for (const key of this._keys) { // Set instead of remove to avoid loading the value from storage. this.values.set(key, null); } this._keys.clear(); } [Symbol.iterator](): UnorderedMapIterator { return new UnorderedMapIterator(this); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions(options?: GetOptions): { [Symbol.iterator](): UnorderedMapIterator; } { return { [Symbol.iterator]: () => new UnorderedMapIterator(this, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): [string, DataType][] { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Extends the current collection with the passed in array of key-value pairs. * * @param keyValuePairs - The key-value pairs to extend the collection with. */ extend(keyValuePairs: [string, DataType][]) { for (const [key, value] of keyValuePairs) { this.set(key, value); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct( data: UnorderedMap ): UnorderedMap { // removing readonly modifier type MutableUnorderedMap = Mutable>; const map = new UnorderedMap(data.prefix) as MutableUnorderedMap; // reconstruct keys Vector map._keys = new Vector(`${data.prefix}u`); map._keys.length = data._keys.length; // reconstruct values LookupMap map.values = new LookupMap(`${data.prefix}m`); return map as UnorderedMap; } keys({ start, limit }): string[] { const ret = []; if (start === undefined) { start = 0; } if (limit == undefined) { limit = this.length - start; } for (let i = start; i < start + limit; i++) { ret.push(this._keys.get(i)); } return ret; } } /** * An iterator for the UnorderedMap collection. */ class UnorderedMapIterator { private keys: VectorIterator; private map: LookupMap; /** * @param unorderedMap - The unordered map collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor( unorderedMap: UnorderedMap, private options?: GetOptions ) { this.keys = new VectorIterator(unorderedMap._keys); this.map = unorderedMap.values; this.subtype = unorderedMap.subtype; } /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-empty-function */ subtype(): any {} next(): { value: [string | null, DataType | null]; done: boolean } { const key = this.keys.next(); if (key.done) { return { value: [key.value, null], done: key.done }; } const valueAndIndex = this.map.get(key.value); assert(valueAndIndex !== null, ERR_INCONSISTENT_STATE); return { done: key.done, value: [ key.value, getValueWithOptions( this.subtype(), encode(valueAndIndex[0]), this.options ), ], }; } } ================================================ FILE: packages/near-sdk-js/src/collections/unordered-set.ts ================================================ import * as near from "../api"; import { assert, serializeValueWithOptions, ERR_INCONSISTENT_STATE, encode, } from "../utils"; import { Vector, VectorIterator } from "./vector"; import { Mutable } from "../utils"; import { GetOptions } from "../types/collections"; function serializeIndex(index: number) { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); return array; } function deserializeIndex(rawIndex: Uint8Array): number { const [data] = new Uint32Array(rawIndex.buffer); return data; } /** * An unordered set that stores data in NEAR storage. */ export class UnorderedSet { readonly elementIndexPrefix: string; readonly _elements: Vector; /** * @param prefix - The byte prefix to use when storing elements inside this collection. */ constructor(readonly prefix: string) { this.elementIndexPrefix = `${prefix}i`; this._elements = new Vector(`${prefix}e`); } /** * The number of elements stored in the collection. */ get length(): number { return this._elements.length; } /** * Checks whether the collection is empty. */ isEmpty(): boolean { return this._elements.isEmpty(); } /** * Checks whether the collection contains the value. * * @param element - The value for which to check the presence. * @param options - Options for storing data. */ contains( element: DataType, options?: Pick, "serializer"> ): boolean { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); return near.storageHasKey(indexLookup); } /** * If the set did not have this value present, `true` is returned. * If the set did have this value present, `false` is returned. * * @param element - The value to store in the collection. * @param options - Options for storing the data. */ set( element: DataType, options?: Pick, "serializer"> ): boolean { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); if (near.storageRead(indexLookup)) { return false; } const nextIndex = this.length; const nextIndexRaw = serializeIndex(nextIndex); near.storageWriteRaw(encode(indexLookup), nextIndexRaw); this._elements.push(element, options); return true; } /** * Returns true if the element was present in the set. * * @param element - The entry to remove. * @param options - Options for retrieving and storing data. */ remove(element: DataType, options?: GetOptions): boolean { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); const indexRaw = near.storageReadRaw(encode(indexLookup)); if (!indexRaw) { return false; } // If there is only one element then swap remove simply removes it without // swapping with the last element. if (this.length === 1) { near.storageRemove(indexLookup); const index = deserializeIndex(indexRaw); this._elements.swapRemove(index); return true; } // If there is more than one element then swap remove swaps it with the last // element. const lastElement = this._elements.get(this.length - 1, options); assert(!!lastElement, ERR_INCONSISTENT_STATE); near.storageRemove(indexLookup); // If the removed element was the last element from keys, then we don't need to // reinsert the lookup back. if (lastElement !== element) { const lastLookupElement = this.elementIndexPrefix + serializeValueWithOptions(lastElement, options); near.storageWriteRaw(encode(lastLookupElement), indexRaw); } const index = deserializeIndex(indexRaw); this._elements.swapRemove(index); return true; } /** * Remove all of the elements stored within the collection. */ clear(options?: Pick, "serializer">): void { for (const element of this._elements) { const indexLookup = this.elementIndexPrefix + serializeValueWithOptions(element, options); near.storageRemove(indexLookup); } this._elements.clear(); } [Symbol.iterator](): VectorIterator { return this._elements[Symbol.iterator](); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions(options?: GetOptions): { [Symbol.iterator](): VectorIterator; } { return { [Symbol.iterator]: () => new VectorIterator(this._elements, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): DataType[] { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements: DataType[]): void { for (const element of elements) { this.set(element); } } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct( data: UnorderedSet ): UnorderedSet { // removing readonly modifier type MutableUnorderedSet = Mutable>; const set = new UnorderedSet(data.prefix) as MutableUnorderedSet; // reconstruct Vector const elementsPrefix = data.prefix + "e"; set._elements = new Vector(elementsPrefix); set._elements.length = data._elements.length; return set as UnorderedSet; } elements({ options, start, limit, }: { options?: GetOptions; start?: number; limit?: number; }): DataType[] { const ret = []; if (start === undefined) { start = 0; } if (limit == undefined) { limit = this.length - start; } for (let i = start; i < start + limit; i++) { ret.push(this._elements.get(i, options)); } return ret; } } ================================================ FILE: packages/near-sdk-js/src/collections/vector.ts ================================================ import * as near from "../api"; import { assert, getValueWithOptions, serializeValueWithOptions, ERR_INCONSISTENT_STATE, ERR_INDEX_OUT_OF_BOUNDS, str, bytes, } from "../utils"; import { GetOptions } from "../types/collections"; import { SubType } from "./subtype"; function indexToKey(prefix: string, index: number): string { const data = new Uint32Array([index]); const array = new Uint8Array(data.buffer); const key = str(array); return prefix + key; } /** * An iterable implementation of vector that stores its content on the trie. * Uses the following map: index -> element */ export class Vector extends SubType { /** * @param prefix - The byte prefix to use when storing elements inside this collection. * @param length - The initial length of the collection. By default 0. */ constructor(readonly prefix: string, public length = 0) { super(); } /** * Checks whether the collection is empty. */ isEmpty(): boolean { return this.length === 0; } /** * Get the data stored at the provided index. * * @param index - The index at which to look for the data. * @param options - Options for retrieving the data. */ get( index: number, options?: Omit, "serializer"> ): DataType | null { if (index >= this.length) { return options?.defaultValue ?? null; } const storageKey = indexToKey(this.prefix, index); const value = near.storageReadRaw(bytes(storageKey)); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Removes an element from the vector and returns it in serialized form. * The removed element is replaced by the last element of the vector. * Does not preserve ordering, but is `O(1)`. * * @param index - The index at which to remove the element. * @param options - Options for retrieving and storing the data. */ swapRemove(index: number, options?: GetOptions): DataType | null { assert(index < this.length, ERR_INDEX_OUT_OF_BOUNDS); if (index + 1 === this.length) { return this.pop(options); } const key = indexToKey(this.prefix, index); const last = this.pop(options); assert( near.storageWriteRaw( bytes(key), serializeValueWithOptions(last, options) ), ERR_INCONSISTENT_STATE ); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Adds data to the collection. * * @param element - The data to store. * @param options - Options for storing the data. */ push( element: DataType, options?: Pick, "serializer"> ): void { const key = indexToKey(this.prefix, this.length); this.length += 1; near.storageWriteRaw( bytes(key), serializeValueWithOptions(element, options) ); } /** * Removes and retrieves the element with the highest index. * * @param options - Options for retrieving the data. */ pop(options?: Omit, "serializer">): DataType | null { if (this.isEmpty()) { return options?.defaultValue ?? null; } const lastIndex = this.length - 1; const lastKey = indexToKey(this.prefix, lastIndex); this.length -= 1; assert(near.storageRemoveRaw(bytes(lastKey)), ERR_INCONSISTENT_STATE); const value = near.storageGetEvictedRaw(); return getValueWithOptions(this.subtype(), value, options); } /** * Replaces the data stored at the provided index with the provided data and returns the previously stored data. * * @param index - The index at which to replace the data. * @param element - The data to replace with. * @param options - Options for retrieving and storing the data. */ replace( index: number, element: DataType, options?: GetOptions ): DataType { assert(index < this.length, ERR_INDEX_OUT_OF_BOUNDS); const key = indexToKey(this.prefix, index); assert( near.storageWriteRaw( bytes(key), serializeValueWithOptions(element, options) ), ERR_INCONSISTENT_STATE ); const value = near.storageGetEvictedRaw(); options = this.set_reconstructor(options); return getValueWithOptions(this.subtype(), value, options); } /** * Extends the current collection with the passed in array of elements. * * @param elements - The elements to extend the collection with. */ extend(elements: DataType[]): void { for (const element of elements) { this.push(element); } } [Symbol.iterator](): VectorIterator { return new VectorIterator(this); } /** * Create a iterator on top of the default collection iterator using custom options. * * @param options - Options for retrieving and storing the data. */ private createIteratorWithOptions(options?: GetOptions): { [Symbol.iterator](): VectorIterator; } { return { [Symbol.iterator]: () => new VectorIterator(this, options), }; } /** * Return a JavaScript array of the data stored within the collection. * * @param options - Options for retrieving and storing the data. */ toArray(options?: GetOptions): DataType[] { const array = []; const iterator = options ? this.createIteratorWithOptions(options) : this; for (const value of iterator) { array.push(value); } return array; } /** * Remove all of the elements stored within the collection. */ clear(): void { for (let index = 0; index < this.length; index++) { const key = indexToKey(this.prefix, index); near.storageRemoveRaw(bytes(key)); } this.length = 0; } /** * Serialize the collection. * * @param options - Options for storing the data. */ serialize(options?: Pick, "serializer">): Uint8Array { return serializeValueWithOptions(this, options); } /** * Converts the deserialized data from storage to a JavaScript instance of the collection. * * @param data - The deserialized data to create an instance from. */ static reconstruct(data: Vector): Vector { const vector = new Vector(data.prefix, data.length); return vector; } } /** * An iterator for the Vector collection. */ export class VectorIterator { private current = 0; /** * @param vector - The vector collection to create an iterator for. * @param options - Options for retrieving and storing data. */ constructor( private vector: Vector, private readonly options?: GetOptions ) {} next(): { value: DataType | null; done: boolean; } { if (this.current >= this.vector.length) { return { value: null, done: true }; } const value = this.vector.get(this.current, this.options); this.current += 1; return { value, done: false }; } } ================================================ FILE: packages/near-sdk-js/src/index.ts ================================================ export * from "./collections"; export * from "./types"; export * as near from "./api"; export * from "./near-bindgen"; export * from "./promise"; export * from "./utils"; ================================================ FILE: packages/near-sdk-js/src/near-bindgen.ts ================================================ import * as near from "./api"; import { deserialize, serialize, bytes, encode, decodeObj2class, } from "./utils"; type EmptyParameterObject = Record; type AnyObject = Record; // eslint-disable-next-line @typescript-eslint/no-explicit-any type DecoratorFunction = any>( target: object, key: string | symbol, descriptor: TypedPropertyDescriptor ) => void; /** * Tells the SDK to use this function as the migration function of the contract. * The migration function will ignore te existing state. * @param _empty - An empty object. */ export function migrate(_empty: EmptyParameterObject): DecoratorFunction { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function any>( _target: object, _key: string | symbol, _descriptor: TypedPropertyDescriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ): void {}; } /** * Tells the SDK to use this function as the initialization function of the contract. * * @param _empty - An empty object. */ export function initialize(_empty: EmptyParameterObject): DecoratorFunction { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function any>( _target: object, _key: string | symbol, _descriptor: TypedPropertyDescriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ): void {}; } /** * Tells the SDK to expose this function as a view function. * * @param _empty - An empty object. */ export function view(_empty: EmptyParameterObject): DecoratorFunction { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function any>( _target: object, _key: string | symbol, _descriptor: TypedPropertyDescriptor // eslint-disable-next-line @typescript-eslint/no-empty-function ): void {}; } /** * Tells the SDK to expose this function as a call function. * Adds the necessary checks if the function is private or payable. * * @param options - Options to configure the function behaviour. * @param options.privateFunction - Whether the function can be called by other contracts. * @param options.payableFunction - Whether the function can accept an attached deposit. * @returns */ export function call(options: { privateFunction?: boolean; payableFunction?: boolean; }): DecoratorFunction; export function call({ privateFunction = false, payableFunction = false, }: { privateFunction?: boolean; payableFunction?: boolean; }): DecoratorFunction { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function any>( _target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor ): void { const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore descriptor.value = function ( ...args: Parameters ): ReturnType { if ( privateFunction && near.predecessorAccountId() !== near.currentAccountId() ) { throw new Error("Function is private"); } if (!payableFunction && near.attachedDeposit() > 0n) { throw new Error("Function is not payable"); } return originalMethod.apply(this, args); }; }; } /** * The interface that a middleware has to implement in order to be used as a middleware function/class. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any interface Middleware> { /** * The method that gets called with the same arguments that are passed to the function it is wrapping. * * @param args - Arguments that will be passed to the function - immutable. */ (...args: Arguments): void; } /** * Tells the SDK to apply an array of passed in middleware to the function execution. * * @param middlewares - The middlewares to be executed. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function middleware>( ...middlewares: Middleware[] ): DecoratorFunction { // eslint-disable-next-line @typescript-eslint/no-explicit-any return function any>( _target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor ): void { const originalMethod = descriptor.value; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore descriptor.value = function (...args: Arguments): ReturnType { try { middlewares.forEach((middleware) => middleware(...args)); } catch (error) { throw new Error(error); } return originalMethod.apply(this, args); }; }; } /** * Extends this class with the methods needed to make the contract storable/serializable and readable/deserializable to and from the blockchain. * Also tells the SDK to capture and expose all view, call and initialize functions. * Tells the SDK whether the contract requires initialization and whether to use a custom serialization/deserialization function when storing/reading the state. * * @param options - Options to configure the contract behaviour. * @param options.requireInit - Whether the contract requires initialization. * @param options.serializer - Custom serializer function to use for storing the contract state. * @param options.deserializer - Custom deserializer function to use for reading the contract state. */ export function NearBindgen(options: { requireInit?: boolean; serializer?(value: unknown): Uint8Array; deserializer?(value: Uint8Array): unknown; // eslint-disable-next-line @typescript-eslint/no-explicit-any }): any; export function NearBindgen({ requireInit = false, serializer = serialize, deserializer = deserialize, }: { requireInit?: boolean; serializer?(value: unknown): Uint8Array; deserializer?(value: Uint8Array): unknown; }) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (target: T) => { return class extends target { static _create() { return new target(); } static _getState(): unknown | null { const rawState = near.storageReadRaw(bytes("STATE")); return rawState ? this._deserialize(rawState) : null; } static _saveToStorage(objectToSave: unknown): void { near.storageWriteRaw(bytes("STATE"), this._serialize(objectToSave)); } static _getArgs(): unknown { return JSON.parse(near.input() || "{}"); } static _serialize(value: unknown, forReturn = false): Uint8Array { if (forReturn) { return encode( JSON.stringify(value, (_, value) => typeof value === "bigint" ? `${value}` : value ) ); } return serializer(value); } static _deserialize(value: Uint8Array): unknown { return deserializer(value); } static _reconstruct(classObject: object, plainObject: AnyObject): object { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore if (classObject.constructor.schema === undefined) { for (const item in classObject) { const reconstructor = classObject[item].constructor?.reconstruct; classObject[item] = reconstructor ? reconstructor(plainObject[item]) : plainObject[item]; } return classObject; } return decodeObj2class(classObject, plainObject); } static _requireInit(): boolean { return requireInit; } }; }; } declare module "./" { /** * A macro that reads the WASM code from the specified path at compile time. * * @param pathToWasm - The path to the WASM file to read code from. */ export function includeBytes(pathToWasm: string): Uint8Array; } ================================================ FILE: packages/near-sdk-js/src/promise.ts ================================================ import { assert, PromiseIndex } from "./utils"; import * as near from "./api"; import { Balance, PublicKey, AccountId, Gas, GasWeight } from "./types"; import { Nonce } from "./types/primitives"; /** * A promise action which can be executed on the NEAR blockchain. */ export abstract class PromiseAction { /** * The method that describes how a promise action adds it's _action_ to the promise batch with the provided index. * * @param promiseIndex - The index of the promise batch to attach the action to. */ abstract add(promiseIndex: PromiseIndex): void; } /** * A create account promise action. * * @extends {PromiseAction} */ export class CreateAccount extends PromiseAction { add(promiseIndex: PromiseIndex) { near.promiseBatchActionCreateAccount(promiseIndex); } } /** * A deploy contract promise action. * * @extends {PromiseAction} */ export class DeployContract extends PromiseAction { /** * @param code - The code of the contract to be deployed. */ constructor(public code: Uint8Array) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionDeployContract(promiseIndex, this.code); } } /** * A function call promise action. * * @extends {PromiseAction} */ export class FunctionCall extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor( public functionName: string, public args: string, public amount: Balance, public gas: Gas ) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionFunctionCall( promiseIndex, this.functionName, this.args, this.amount, this.gas ); } } /** * A function call raw promise action. * * @extends {PromiseAction} */ export class FunctionCallRaw extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ constructor( public functionName: string, public args: Uint8Array, public amount: Balance, public gas: Gas ) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionFunctionCallRaw( promiseIndex, this.functionName, this.args, this.amount, this.gas ); } } /** * A function call weight promise action. * * @extends {PromiseAction} */ export class FunctionCallWeight extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor( public functionName: string, public args: string, public amount: Balance, public gas: Gas, public weight: GasWeight ) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionFunctionCallWeight( promiseIndex, this.functionName, this.args, this.amount, this.gas, this.weight ); } } /** * A function call weight raw promise action. * * @extends {PromiseAction} */ export class FunctionCallWeightRaw extends PromiseAction { /** * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ constructor( public functionName: string, public args: Uint8Array, public amount: Balance, public gas: Gas, public weight: GasWeight ) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionFunctionCallWeightRaw( promiseIndex, this.functionName, this.args, this.amount, this.gas, this.weight ); } } /** * A transfer promise action. * * @extends {PromiseAction} */ export class Transfer extends PromiseAction { /** * @param amount - The amount of NEAR to transfer. */ constructor(public amount: Balance) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionTransfer(promiseIndex, this.amount); } } /** * A stake promise action. * * @extends {PromiseAction} */ export class Stake extends PromiseAction { /** * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ constructor(public amount: Balance, public publicKey: PublicKey) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionStake( promiseIndex, this.amount, this.publicKey.data ); } } /** * A add full access key promise action. * * @extends {PromiseAction} */ export class AddFullAccessKey extends PromiseAction { /** * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ constructor(public publicKey: PublicKey, public nonce: Nonce) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionAddKeyWithFullAccess( promiseIndex, this.publicKey.data, this.nonce ); } } /** * A add access key promise action. * * @extends {PromiseAction} */ export class AddAccessKey extends PromiseAction { /** * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ constructor( public publicKey: PublicKey, public allowance: Balance, public receiverId: AccountId, public functionNames: string, public nonce: Nonce ) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionAddKeyWithFunctionCall( promiseIndex, this.publicKey.data, this.nonce, this.allowance, this.receiverId, this.functionNames ); } } /** * A delete key promise action. * * @extends {PromiseAction} */ export class DeleteKey extends PromiseAction { /** * @param publicKey - The public key to delete from the account. */ constructor(public publicKey: PublicKey) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionDeleteKey(promiseIndex, this.publicKey.data); } } /** * A delete account promise action. * * @extends {PromiseAction} */ export class DeleteAccount extends PromiseAction { /** * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ constructor(public beneficiaryId: AccountId) { super(); } add(promiseIndex: PromiseIndex) { near.promiseBatchActionDeleteAccount(promiseIndex, this.beneficiaryId); } } class PromiseSingle { constructor( public accountId: AccountId, public actions: PromiseAction[], public after: NearPromise | null, public promiseIndex: PromiseIndex | null ) {} constructRecursively(): PromiseIndex { if (this.promiseIndex !== null) { return this.promiseIndex; } const promiseIndex = this.after ? near.promiseBatchThen(this.after.constructRecursively(), this.accountId) : near.promiseBatchCreate(this.accountId); this.actions.forEach((action) => action.add(promiseIndex)); this.promiseIndex = promiseIndex; return promiseIndex; } } export class PromiseJoint { constructor( public promiseA: NearPromise, public promiseB: NearPromise, public promiseIndex: PromiseIndex | null ) {} constructRecursively(): PromiseIndex { if (this.promiseIndex !== null) { return this.promiseIndex; } const result = near.promiseAnd( this.promiseA.constructRecursively(), this.promiseB.constructRecursively() ); this.promiseIndex = result; return result; } } type PromiseSubtype = PromiseSingle | PromiseJoint; /** * A high level class to construct and work with NEAR promises. */ export class NearPromise { /** * @param subtype - The subtype of the promise. * @param shouldReturn - Whether the promise should return. */ constructor(private subtype: PromiseSubtype, private shouldReturn: boolean) {} /** * Creates a new promise to the provided account ID. * * @param accountId - The account ID on which to call the promise. */ static new(accountId: AccountId): NearPromise { const subtype = new PromiseSingle(accountId, [], null, null); return new NearPromise(subtype, false); } private addAction(action: PromiseAction): NearPromise { if (this.subtype instanceof PromiseJoint) { throw new Error("Cannot add action to a joint promise."); } this.subtype.actions.push(action); return this; } /** * Creates a create account promise action and adds it to the current promise. */ createAccount(): NearPromise { return this.addAction(new CreateAccount()); } /** * Creates a deploy contract promise action and adds it to the current promise. * * @param code - The code of the contract to be deployed. */ deployContract(code: Uint8Array): NearPromise { return this.addAction(new DeployContract(code)); } /** * Creates a function call promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCall( functionName: string, args: string, amount: Balance, gas: Gas ): NearPromise { return this.addAction(new FunctionCall(functionName, args, amount, gas)); } /** * Creates a function call raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. */ functionCallRaw( functionName: string, args: Uint8Array, amount: Balance, gas: Gas ): NearPromise { return this.addAction(new FunctionCallRaw(functionName, args, amount, gas)); } /** * Creates a function call weight promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The utf-8 string arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeight( functionName: string, args: string, amount: Balance, gas: Gas, weight: GasWeight ): NearPromise { return this.addAction( new FunctionCallWeight(functionName, args, amount, gas, weight) ); } /** * Creates a function call weight raw promise action and adds it to the current promise. * * @param functionName - The name of the function to be called. * @param args - The arguments to be passed to the function. * @param amount - The amount of NEAR to attach to the call. * @param gas - The amount of Gas to attach to the call. * @param weight - The weight of unused Gas to use. */ functionCallWeightRaw( functionName: string, args: Uint8Array, amount: Balance, gas: Gas, weight: GasWeight ): NearPromise { return this.addAction( new FunctionCallWeightRaw(functionName, args, amount, gas, weight) ); } /** * Creates a transfer promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. */ transfer(amount: Balance): NearPromise { return this.addAction(new Transfer(amount)); } /** * Creates a stake promise action and adds it to the current promise. * * @param amount - The amount of NEAR to transfer. * @param publicKey - The public key to use for staking. */ stake(amount: Balance, publicKey: PublicKey): NearPromise { return this.addAction(new Stake(amount, publicKey)); } /** * Creates a add full access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a full access key. */ addFullAccessKey(publicKey: PublicKey): NearPromise { return this.addFullAccessKeyWithNonce(publicKey, 0n); } /** * Creates a add full access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a full access key. * @param nonce - The nonce to use. */ addFullAccessKeyWithNonce(publicKey: PublicKey, nonce: Nonce): NearPromise { return this.addAction(new AddFullAccessKey(publicKey, nonce)); } /** * Creates a add access key promise action and adds it to the current promise. * Uses 0n as the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. */ addAccessKey( publicKey: PublicKey, allowance: Balance, receiverId: AccountId, functionNames: string ): NearPromise { return this.addAccessKeyWithNonce( publicKey, allowance, receiverId, functionNames, 0n ); } /** * Creates a add access key promise action and adds it to the current promise. * Allows you to specify the nonce. * * @param publicKey - The public key to add as a access key. * @param allowance - The allowance for the key in yoctoNEAR. * @param receiverId - The account ID of the receiver. * @param functionNames - The names of functions to authorize. * @param nonce - The nonce to use. */ addAccessKeyWithNonce( publicKey: PublicKey, allowance: Balance, receiverId: AccountId, functionNames: string, nonce: Nonce ): NearPromise { return this.addAction( new AddAccessKey(publicKey, allowance, receiverId, functionNames, nonce) ); } /** * Creates a delete key promise action and adds it to the current promise. * * @param publicKey - The public key to delete from the account. */ deleteKey(publicKey: PublicKey): NearPromise { return this.addAction(new DeleteKey(publicKey)); } /** * Creates a delete account promise action and adds it to the current promise. * * @param beneficiaryId - The beneficiary of the account deletion - the account to receive all of the remaining funds of the deleted account. */ deleteAccount(beneficiaryId: AccountId): NearPromise { return this.addAction(new DeleteAccount(beneficiaryId)); } /** * Joins the provided promise with the current promise, making the current promise a joint promise subtype. * * @param other - The promise to join with the current promise. */ and(other: NearPromise): NearPromise { const subtype = new PromiseJoint(this, other, null); return new NearPromise(subtype, false); } /** * Adds a callback to the current promise. * * @param other - The promise to be executed as the promise. */ then(other: NearPromise): NearPromise { assert( other.subtype instanceof PromiseSingle, "Cannot callback joint promise." ); assert( other.subtype.after === null, "Cannot callback promise which is already scheduled after another" ); other.subtype.after = this; return other; } /** * Sets the shouldReturn field to true. */ asReturn(): NearPromise { this.shouldReturn = true; return this; } /** * Recursively goes through the current promise to get the promise index. */ constructRecursively(): PromiseIndex { const result = this.subtype.constructRecursively(); if (this.shouldReturn) { near.promiseReturn(result); } return result; } /** * Called by NearBindgen, when return object is a NearPromise instance. */ onReturn() { this.asReturn().constructRecursively(); } /** * Attach the promise to transaction but does not return it. The promise will be executed, but * whether it success or not will not affect the transaction result. If you want the promise fail * also makes the transaction fail, you can simply return the promise from a @call method. */ build(): PromiseIndex { return this.constructRecursively(); } } export type PromiseOrValue = NearPromise | T; ================================================ FILE: packages/near-sdk-js/src/types/account_id.ts ================================================ /** * A string that represents a NEAR account ID. */ export type AccountId = string; ================================================ FILE: packages/near-sdk-js/src/types/collections.ts ================================================ /** * Options for retrieving and storing data in the SDK collections. */ export interface GetOptions { /** * A constructor function to call after deserializing a value. Typically this is a constructor of the class you are storing. * * @param value - The value returned from deserialization - either the provided `deserializer` or default deserialization function. */ reconstructor?(value: unknown): DataType; /** * A default value to return if the original value is not present or null. */ defaultValue?: DataType; /** * A serializer function to customize the serialization of the collection for this call. * * @param valueToSerialize - The value that will be serialized - either the `DataType` or a unknown value. */ serializer?(valueToSerialize: unknown): Uint8Array; /** * A deserializer function to customize the deserialization of values after reading from NEAR storage for this call. * * @param valueToDeserialize - The Uint8Array retrieved from NEAR storage to deserialize. */ deserializer?(valueToDeserialize: Uint8Array): unknown; } ================================================ FILE: packages/near-sdk-js/src/types/gas.ts ================================================ /** * The Gas amount specified in yoctoNEAR. */ export type Gas = bigint; /** * One TGas - Tera Gas. 10^12 yoctoNEAR. */ export const ONE_TERA_GAS: Gas = 1_000_000_000_000n; ================================================ FILE: packages/near-sdk-js/src/types/index.ts ================================================ export * from "./account_id"; export * from "./gas"; export * from "./primitives"; export * from "./public_key"; export * from "./vm_types"; ================================================ FILE: packages/near-sdk-js/src/types/primitives.ts ================================================ /** * The amount of storage used in yoctoNEAR. */ export type StorageUsage = bigint; /** * A large integer representing the block height. */ export type BlockHeight = bigint; /** * A large integer representing the epoch height. */ export type EpochHeight = bigint; /** * The amount of tokens in yoctoNEAR. */ export type Balance = bigint; /** * A large integer representing the nonce. */ export type Nonce = bigint; /** * The amount of Gas Weight in integers - whole numbers. */ export type GasWeight = bigint; /** * One yoctoNEAR. 10^-24 NEAR. */ export const ONE_YOCTO: Balance = 1n; /** * One NEAR. 1 NEAR = 10^24 yoctoNEAR. */ export const ONE_NEAR: Balance = 1_000_000_000_000_000_000_000_000n; ================================================ FILE: packages/near-sdk-js/src/types/public_key.ts ================================================ import { base58 } from "@scure/base"; import { concat } from "../utils"; export enum CurveType { ED25519 = 0, SECP256K1 = 1, } enum DataLength { ED25519 = 32, SECP256K1 = 64, } function getCurveType(curveType: CurveType | number): CurveType { switch (curveType) { case CurveType.ED25519: case CurveType.SECP256K1: return curveType; default: throw new UnknownCurve(); } } function dataLength(curveType: CurveType | number): DataLength { switch (curveType) { case CurveType.ED25519: case CurveType.SECP256K1: return { [CurveType.ED25519]: DataLength.ED25519, [CurveType.SECP256K1]: DataLength.SECP256K1, }[curveType]; default: throw new UnknownCurve(); } } function splitKeyTypeData(value: string): [CurveType, string] { const idx = value.indexOf(":"); if (idx >= 0) { return [ curveTypeFromStr(value.substring(0, idx)), value.substring(idx + 1), ]; } else { return [CurveType.ED25519, value]; } } export function curveTypeFromStr(value: string): CurveType { switch (value) { case "ed25519": return CurveType.ED25519; case "secp256k1": return CurveType.SECP256K1; default: throw new UnknownCurve(); } } export class ParsePublicKeyError extends Error {} export class InvalidLengthError extends ParsePublicKeyError { constructor(public length: number, public expectedLength: number) { super(`Invalid length: ${length}. Expected: ${expectedLength}`); } } export class Base58Error extends ParsePublicKeyError { constructor(public error: string) { super(`Base58 error: ${error}`); } } export class UnknownCurve extends ParsePublicKeyError { constructor() { super("Unknown curve"); } } /** * A abstraction on top of the NEAR public key string. * Public key in a binary format with base58 string serialization with human-readable curve. * The key types currently supported are `secp256k1` and `ed25519`. */ export class PublicKey { /** * The actual value of the public key. */ public data: Uint8Array; private type: CurveType; /** * @param data - The string you want to create a PublicKey from. */ constructor(data: Uint8Array) { const curveLenght = dataLength(data[0]); if (data.length !== curveLenght + 1) { throw new InvalidLengthError(data.length, curveLenght + 1); } this.type = getCurveType(data[0]); this.data = data; } /** * The curve type of the public key. */ curveType(): CurveType { return this.type; } /** * Create a public key from a public key string. * * @param publicKeyString - The public key string you want to create a PublicKey from. */ static fromString(publicKeyString: string) { const [curve, keyData] = splitKeyTypeData(publicKeyString); let data: Uint8Array; try { data = base58.decode(keyData); } catch (error) { throw new Base58Error(error.message); } return new PublicKey(concat(new Uint8Array([curve]), data)); } } ================================================ FILE: packages/near-sdk-js/src/types/vm_types.ts ================================================ /** * The index for NEAR receipts. */ export type ReceiptIndex = bigint; /** * The index for iterators. */ export type IteratorIndex = bigint; /** * A Promise result in near can be one of: * - NotReady = 0 - the promise you are specifying is still not ready, not yet failed nor successful. * - Successful = 1 - the promise has been successfully executed and you can retrieve the resulting value. * - Failed = 2 - the promise execution has failed. */ export enum PromiseResult { NotReady = 0, Successful = 1, Failed = 2, } /** * A promise error can either be due to the promise failing or not yet being ready. */ export enum PromiseError { Failed, NotReady, } ================================================ FILE: packages/near-sdk-js/src/utils.ts ================================================ import { GetOptions } from "./types/collections"; import { cloneDeep } from "lodash-es"; export interface Env { uint8array_to_latin1_string(a: Uint8Array): string; uint8array_to_utf8_string(a: Uint8Array): string; latin1_string_to_uint8array(s: string): Uint8Array; utf8_string_to_uint8array(s: string): Uint8Array; } declare const env: Env; /** * A PromiseIndex which represents the ID of a NEAR Promise. */ export type PromiseIndex = number | bigint; /** * A number that specifies the amount of NEAR in yoctoNEAR. */ export type NearAmount = number | bigint; /** * A number that specifies the ID of a register in the NEAR WASM virtual machine. */ export type Register = number | bigint; const TYPE_KEY = "typeInfo"; enum TypeBrand { BIGINT = "bigint", DATE = "date", } export const ERR_INCONSISTENT_STATE = "The collection is an inconsistent state. Did previous smart contract execution terminate unexpectedly?"; export const ERR_INDEX_OUT_OF_BOUNDS = "Index out of bounds"; const ACCOUNT_ID_REGEX = /^(([a-z\d]+[-_])*[a-z\d]+\.)*([a-z\d]+[-_])*[a-z\d]+$/; /** * Concat two Uint8Array * @param array1 * @param array2 * @returns the concatenation of two array */ export function concat(array1: Uint8Array, array2: Uint8Array): Uint8Array { const mergedArray = new Uint8Array(array1.length + array2.length); mergedArray.set(array1); mergedArray.set(array2, array1.length); return mergedArray; } /** * Asserts that the expression passed to the function is truthy, otherwise throws a new Error with the provided message. * * @param expression - The expression to be asserted. * @param message - The error message to be printed. */ export function assert( expression: unknown, message: string ): asserts expression { if (!expression) { throw new Error("assertion failed: " + message); } } export type Mutable = { -readonly [P in keyof T]: T[P] }; export function getValueWithOptions( subDatatype: unknown, value: Uint8Array | null, options: Omit, "serializer"> = {} ): DataType | null { if (value === null) { return options?.defaultValue ?? null; } const deserializer = options.deserializer || deserialize; // here is an obj let deserialized = deserializer(value); if (deserialized === undefined || deserialized === null) { return options?.defaultValue ?? null; } if (options?.reconstructor) { // example: // { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} const collection = options.reconstructor(deserialized); if ( subDatatype !== undefined && // eslint-disable-next-line no-prototype-builtins subDatatype.hasOwnProperty("class") && // eslint-disable-next-line no-prototype-builtins subDatatype["class"].hasOwnProperty("value") ) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore collection.subtype = function () { // example: {class: UnorderedMap, value: UnorderedMap} return subDatatype["class"]["value"]; }; } return collection; } // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} if (subDatatype !== undefined) { // subtype info is a class constructor, Such as Car if (typeof subDatatype === "function") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore deserialized = decodeObj2class(new subDatatype(), deserialized); } else if (typeof subDatatype === "object") { // normal collections of array, map; subtype will be: // {map: { key: 'string', value: 'string' }} or {array: {value: 'string'}} .. // eslint-disable-next-line no-prototype-builtins if (subDatatype.hasOwnProperty("map")) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const mkey in deserialized) { if (subDatatype["map"]["value"] !== "string") { deserialized[mkey] = decodeObj2class( new subDatatype["map"]["value"](), value[mkey] ); } } // eslint-disable-next-line no-prototype-builtins } else if (subDatatype.hasOwnProperty("array")) { const new_vec = []; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore for (const k in deserialized) { if (subDatatype["array"]["value"] !== "string") { new_vec.push( decodeObj2class(new subDatatype["array"]["value"](), value[k]) ); } } deserialized = new_vec; // eslint-disable-next-line no-prototype-builtins } } } return deserialized as DataType; } export function serializeValueWithOptions( value: DataType, { serializer }: Pick, "serializer"> = { serializer: serialize, } ): Uint8Array { return serializer(value); } export function serialize(valueToSerialize: unknown): Uint8Array { return encode( JSON.stringify(valueToSerialize, function (key, value) { if (typeof value === "bigint") { return { value: value.toString(), [TYPE_KEY]: TypeBrand.BIGINT, }; } if ( typeof this[key] === "object" && this[key] !== null && this[key] instanceof Date ) { return { value: this[key].toISOString(), [TYPE_KEY]: TypeBrand.DATE, }; } return value; }) ); } export function deserialize(valueToDeserialize: Uint8Array): unknown { return JSON.parse(decode(valueToDeserialize), (_, value) => { if ( value !== null && typeof value === "object" && Object.keys(value).length === 2 && Object.keys(value).every((key) => ["value", TYPE_KEY].includes(key)) ) { switch (value[TYPE_KEY]) { case TypeBrand.BIGINT: return BigInt(value["value"]); case TypeBrand.DATE: return new Date(value["value"]); } } return value; }); } export function decodeObj2class(class_instance, obj) { if ( typeof obj != "object" || typeof obj === "bigint" || obj instanceof Date || class_instance.constructor.schema === undefined ) { return obj; } let key; for (key in obj) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const value = obj[key]; if (typeof value == "object") { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const ty = class_instance.constructor.schema[key]; // eslint-disable-next-line no-prototype-builtins if (ty !== undefined && ty.hasOwnProperty("map")) { for (const mkey in value) { if (ty["map"]["value"] === "string") { class_instance[key][mkey] = value[mkey]; } else { class_instance[key][mkey] = decodeObj2class( new ty["map"]["value"](), value[mkey] ); } } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("array")) { for (const k in value) { if (ty["array"]["value"] === "string") { class_instance[key].push(value[k]); } else { class_instance[key].push( decodeObj2class(new ty["array"]["value"](), value[k]) ); } } // eslint-disable-next-line no-prototype-builtins } else if (ty !== undefined && ty.hasOwnProperty("class")) { // => nested_lookup_recordes: {class: UnorderedMap, value: {class: LookupMap }}, class_instance[key] = ty["class"].reconstruct(obj[key]); // eslint-disable-next-line no-prototype-builtins if (ty.hasOwnProperty("value")) { const subtype_value = ty["value"]; class_instance[key].subtype = function () { // example: { collection: {reconstructor: LookupMap.reconstruct, value: 'string'}} // example: UnorderedMap or {class: UnorderedMap, value: 'string'} return subtype_value; }; } } else if (ty !== undefined && typeof ty.reconstruct === "function") { class_instance[key] = ty.reconstruct(obj[key]); } else { // normal case with nested Class, such as field is truck: Truck, class_instance[key] = decodeObj2class(class_instance[key], obj[key]); } } else { class_instance[key] = obj[key]; } } const instance_tmp = cloneDeep(class_instance); for (key in obj) { if ( typeof class_instance[key] == "object" && !(class_instance[key] instanceof Date) ) { class_instance[key] = instance_tmp[key]; } } return class_instance; } /** * Validates the Account ID according to the NEAR protocol * [Account ID rules](https://nomicon.io/DataStructures/Account#account-id-rules). * * @param accountId - The Account ID string you want to validate. */ export function validateAccountId(accountId: string): boolean { return ( accountId.length >= 2 && accountId.length <= 64 && ACCOUNT_ID_REGEX.test(accountId) ); } /** * A subset of NodeJS TextEncoder API */ export class TextEncoder { encode(s: string): Uint8Array { return env.utf8_string_to_uint8array(s); } } /** * A subset of NodeJS TextDecoder API. Only support utf-8 and latin1 encoding. */ export class TextDecoder { constructor(public encoding: string = "utf-8") {} decode(a: Uint8Array): string { if (this.encoding == "utf-8") { return env.uint8array_to_utf8_string(a); } else if (this.encoding == "latin1") { return env.uint8array_to_latin1_string(a); } else { throw new Error("unsupported encoding: " + this.encoding); } } } /** * Convert a string to Uint8Array, each character must have a char code between 0-255. * @param s - string that with only Latin1 character to convert * @returns result Uint8Array */ export function bytes(s: string): Uint8Array { return env.latin1_string_to_uint8array(s); } /** * Convert a Uint8Array to string, each uint8 to the single character of that char code * @param a - Uint8Array to convert * @returns result string */ export function str(a: Uint8Array): string { return env.uint8array_to_latin1_string(a); } /** * Encode the string to Uint8Array with UTF-8 encoding * @param s - String to encode * @returns result Uint8Array */ export function encode(s: string): Uint8Array { return env.utf8_string_to_uint8array(s); } /** * Decode the Uint8Array to string in UTF-8 encoding * @param a - array to decode * @returns result string */ export function decode(a: Uint8Array): string { return env.uint8array_to_utf8_string(a); } /** * When implemented, allow object to be stored as collection key */ export interface IntoStorageKey { into_storage_key(): string; } ================================================ FILE: packages/near-sdk-js/src/version.ts ================================================ import * as fs from "fs"; import { fileURLToPath } from "url"; const PACKAGE_JSON = JSON.parse( fs.readFileSync( fileURLToPath(new URL("../package.json", import.meta.url)), "utf-8" ) ); export const LIB_VERSION: string = PACKAGE_JSON["version"]; ================================================ FILE: packages/near-sdk-js/tsconfig.json ================================================ { "compilerOptions": { "esModuleInterop": true, "lib": ["es2015", "esnext", "dom"], "module": "esnext", "target": "es2020", "moduleResolution": "node", "alwaysStrict": true, "outDir": "./lib", "declaration": true, "preserveSymlinks": true, "preserveWatchOutput": true, "pretty": false, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "noImplicitAny": false, "noImplicitReturns": true, "noUnusedLocals": true, "experimentalDecorators": true, "resolveJsonModule": true, "allowJs": true, "skipLibCheck": true }, "files": [ "src/index.ts", "src/cli/build-tools/near-bindgen-exporter.ts", "src/cli/build-tools/include-bytes.ts", "src/cli/cli.ts", "src/cli/post-install.ts" ] } ================================================ FILE: packages/near-sdk-js/typedoc.json ================================================ { "extends": ["../../typedoc.json"], "entryPoints": ["src"], "entryPointStrategy": "expand" } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - "examples" - "tests" - "packages/*" - "benchmark" ================================================ FILE: tests/.gitignore ================================================ node_modules build ================================================ FILE: tests/README.md ================================================ # NEAR-SDK-JS Tests This tests the functionality of high level APIs of NEAR-SDK-JS. Currently, it directly tests all collections and indirectly tests all decorators, serialization/deserialization, utils, code generation and some important APIs. Majority of near-sdk-js can be seen as tested. # Run tests ``` pnpm install pnpm build pnpm test ``` # Add a new test Create a test contract that covers the API you want to test in `src/`. Add a build command in `build.sh`. Write ava test in `__tests__`. ================================================ FILE: tests/__tests__/abi/abi.ava.js ================================================ import test from "ava"; import { generateAbiSnippet } from "./util.js"; test("Simple function", async (t) => { const abi = await generateAbiSnippet("simple_function.ts"); t.deepEqual(abi.body.functions[0], { name: "add", kind: "view", params: { serialization_type: "json", args: [ { name: "a", type_schema: { type: "number", }, }, { name: "b", type_schema: { type: "number", }, }, ], }, result: { serialization_type: "json", type_schema: { type: "number", }, }, }); }); test("JSON Schema", async (t) => { const abi = await generateAbiSnippet("json_schema.ts"); t.is(abi.body.functions[0].params.args.length, 1); t.is(abi.body.functions[0].params.serialization_type, "json"); t.deepEqual(abi.body.functions[0].params.args[0], { name: "a", type_schema: { type: "number", }, }); t.deepEqual(abi.body.functions[1].params.args[0], { name: "a", type_schema: { type: "string", }, }); t.deepEqual(abi.body.functions[2].params.args, [ { name: "a", type_schema: { type: "boolean", }, }, { name: "b", type_schema: { type: "null", }, }, ]); t.deepEqual(abi.body.functions[3].params.args, [ { name: "a", type_schema: { items: [{ type: "boolean" }], maxItems: 1, minItems: 1, type: "array", }, }, { name: "b", type_schema: { items: [{ type: "boolean" }, { type: "boolean" }], maxItems: 2, minItems: 2, type: "array", }, }, { name: "c", type_schema: { items: [{ type: "boolean" }, { type: "boolean" }, { type: "boolean" }], maxItems: 3, minItems: 3, type: "array", }, }, ]); t.deepEqual(abi.body.functions[4].params.args, [ { name: "a", type_schema: { items: { type: "boolean" }, type: "array", }, }, ]); // typescript use structural type, so no ref to Pair t.deepEqual(abi.body.functions[5].params.args, [ { name: "a", type_schema: { items: [{ type: "number" }, { type: "number" }], type: "array", minItems: 2, maxItems: 2, }, }, { name: "b", type_schema: { $ref: "#/definitions/PairNamed" } }, ]); t.deepEqual(abi.body.root_schema.definitions["PairNamed"], { properties: { first: { type: "number", }, second: { type: "number", }, }, type: "object", }); t.deepEqual(abi.body.root_schema.definitions["IpAddrKind"], { enum: [0, 1], type: "number", }); t.deepEqual(abi.body.root_schema.definitions["IpV4"], { type: "object", properties: { kind: { $ref: "#/definitions/IpAddrKind.V4", }, octets: { items: [ { type: "number" }, { type: "number" }, { type: "number" }, { type: "number" }, ], maxItems: 4, minItems: 4, type: "array", }, }, }); t.deepEqual(abi.body.root_schema.definitions["IpV6"], { type: "object", properties: { kind: { $ref: "#/definitions/IpAddrKind.V6", }, address: { type: "string", }, }, }); t.deepEqual(abi.body.functions[6].params.args, [ { name: "simple", type_schema: { $ref: "#/definitions/IpAddrKind" }, }, { name: "complex", type_schema: { anyOf: [ { $ref: "#/definitions/IpV4", }, { $ref: "#/definitions/IpV6", }, ], }, }, ]); }); test("Modifiers function", async (t) => { const abi = await generateAbiSnippet("modifiers.ts"); t.is(abi.body.functions[0].kind, "view"); t.is(abi.body.functions[0].modifiers, undefined); t.is(abi.body.functions[1].kind, "call"); t.is(abi.body.functions[1].modifiers, undefined); t.is(abi.body.functions[2].kind, "call"); t.deepEqual(abi.body.functions[2].modifiers, ["init"]); t.is(abi.body.functions[3].kind, "call"); t.deepEqual(abi.body.functions[3].modifiers, ["payable"]); t.is(abi.body.functions[4].kind, "call"); t.deepEqual(abi.body.functions[4].modifiers, ["private"]); }); test("Function return", async (t) => { const abi = await generateAbiSnippet("return.ts"); t.deepEqual(abi.body.functions[0], { name: "foo", kind: "view", }); t.deepEqual(abi.body.functions[1], { name: "bar", kind: "view", result: { serialization_type: "json", type_schema: { type: "number", }, }, }); }); ================================================ FILE: tests/__tests__/abi/testcases/json_schema.ts ================================================ import { NearBindgen, near, call, view } from "near-sdk-js"; type Pair = [number, number]; interface PairNamed { first: number; second: number; } enum IpAddrKind { V4, V6, } interface IpV4 { kind: IpAddrKind.V4; octets: [number, number, number, number]; } interface IpV6 { kind: IpAddrKind.V6; address: string; } type IpAddr = IpV4 | IpV6; @NearBindgen({}) export class Contract { @view({}) numeric({ a }: { a: number }) {} @view({}) schema_string({ a }: { a: string }) {} @view({}) schema_other_primitives({ a, b }: { a: boolean; b: null }) {} @view({}) schema_tuples({ a, b, c, }: { a: [boolean]; b: [boolean, boolean]; c: [boolean, boolean, boolean]; }) {} @view({}) schema_array({ a }: { a: boolean[] }) {} @view({}) schema_struct({ a, b }: { a: Pair; b: PairNamed }) {} @view({}) schema_enum({ simple, complex }: { simple: IpAddrKind; complex: IpAddr }) {} } ================================================ FILE: tests/__tests__/abi/testcases/modifiers.ts ================================================ import { NearBindgen, near, call, view, initialize } from "near-sdk-js"; @NearBindgen({}) export class Contract { @view({}) add({ a, b }: { a: number; b: number }): number { return a + b; } @call({}) add2({ a, b }: { a: number; b: number }): number { return a + b; } @initialize({}) add3({ a, b }: { a: number; b: number }): number { return a + b; } @call({ payableFunction: true }) add4({ a, b }: { a: number; b: number }): number { return a + b; } @call({ privateFunction: true }) add5({ a, b }: { a: number; b: number }): number { return a + b; } } ================================================ FILE: tests/__tests__/abi/testcases/return.ts ================================================ import { NearBindgen, near, call, view } from "near-sdk-js"; @NearBindgen({}) export class Contract { @view({}) foo() {} @view({}) bar(): number { return 0; } } ================================================ FILE: tests/__tests__/abi/testcases/simple_function.ts ================================================ import { NearBindgen, near, call, view } from "near-sdk-js"; @NearBindgen({}) export class Contract { @view({}) add({ a, b }: { a: number; b: number }): number { return a + b; } } ================================================ FILE: tests/__tests__/abi/util.js ================================================ import path from "path"; import fs from "fs/promises"; import { fileURLToPath } from "url"; import { runAbiCompilerPlugin } from "near-sdk-js/lib/cli/abi.js"; import { randomBytes } from "crypto"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export async function generateAbiSnippet(filename) { const packageJsonPath = path.join(__dirname, "../../package.json"); const tsConfigJsonPath = path.join(__dirname, "../../tsconfig.json"); const filepath = path.join(__dirname, "testcases", filename); return runAbiCompilerPlugin(filepath, packageJsonPath, tsConfigJsonPath); } ================================================ FILE: tests/__tests__/bytes.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const bytesContract = await root.createSubAccount("bytes-contract"); await bytesContract.deploy("build/bytes.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, bytesContract, ali, bob }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Log expected types work", async (t) => { const { ali, bytesContract } = t.context.accounts; let r = await ali.callRaw(bytesContract, "log_expected_input_tests", ""); t.deepEqual(r.result.receipts_outcome[0].outcome.logs, [ "abc", "水", "333", "\x00\x01\xff", "\xe6\xb0\xb4", "水", "水", ]); }); test("Log unexpected types not logging", async (t) => { const { ali, bytesContract } = t.context.accounts; let r = await ali.callRaw(bytesContract, "log_unexpected_input_tests", ""); // logUtf8 and logUtf16 only works with bytes, trying to log it with string is error t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Expect Uint8Array for message" ) ); t.deepEqual(r.result.receipts_outcome[0].outcome.logs, []); }); test("Log invalid utf-8 sequence panic", async (t) => { const { ali, bytesContract } = t.context.accounts; let r = await ali.callRaw( bytesContract, "log_invalid_utf8_sequence_test", "" ); // console.log(JSON.stringify(r, null, 2)) t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-8 sequence." ); }); test("Log invalid utf-16 sequence panic", async (t) => { const { ali, bytesContract } = t.context.accounts; let r = await ali.callRaw( bytesContract, "log_invalid_utf16_sequence_test", "" ); t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-16 sequence." ); }); function encodeStateKey(k) { return Buffer.from(k).toString("base64"); } test("storage write bytes tests. Any latin1 string: ascii, valid or invalid utf-8 sequence can be convert to Uint8Array correctly", async (t) => { const { ali, bytesContract } = t.context.accounts; await ali.call(bytesContract, "storage_write_bytes", ""); let stateMap = new Map(); // viewState doesn't work, because it tries to convert key to utf-8 string, which is not. So we use viewStateRaw let state = await bytesContract.viewStateRaw(); for (let { key, value } of state) { stateMap.set(key, value); } t.deepEqual( stateMap.get(encodeStateKey("abc")), Buffer.from("def").toString("base64") ); t.deepEqual( stateMap.get(encodeStateKey([0x00, 0x01, 0xff])), Buffer.from([0xe6, 0xb0, 0xb4]).toString("base64") ); t.deepEqual( stateMap.get(encodeStateKey([0xe6, 0xb0, 0xb4])), Buffer.from([0x00, "a".charCodeAt(0), "b".charCodeAt(0)]).toString("base64") ); }); test("storage write utf8, utf8 string convert to Uint8Array tests", async (t) => { const { ali, bytesContract } = t.context.accounts; await ali.call(bytesContract, "storage_write_utf8", ""); let r = await bytesContract.viewRaw("storage_read_utf8", ""); t.deepEqual(r.result, [...Buffer.from("😂", "utf-8")]); }); test("Storage read bytes tests", async (t) => { const { ali, bytesContract } = t.context.accounts; await ali.call(bytesContract, "storage_write_bytes", ""); let r = await bytesContract.viewRaw("storage_read_ascii_bytes", ""); console.log(r); t.deepEqual(r.result, [100, 101, 102]); r = await bytesContract.viewRaw( "storage_read_arbitrary_bytes_key_utf8_sequence_bytes_value", "" ); t.deepEqual(r.result, [0xe6, 0xb0, 0xb4]); r = await bytesContract.viewRaw( "storage_read_utf8_sequence_bytes_key_arbitrary_bytes_value", "" ); t.deepEqual(r.result, [0x00, "a".charCodeAt(0), "b".charCodeAt(0)]); }); test("panic tests", async (t) => { const { ali, bytesContract } = t.context.accounts; let r = await ali.callRaw(bytesContract, "panic_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked:*/ ) ); r = await ali.callRaw(bytesContract, "panic_ascii_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: abc*/ ) ); r = await ali.callRaw(bytesContract, "panic_js_number", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: 356*/ ) ); r = await ali.callRaw(bytesContract, "panic_js_undefined", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked:*/ ) ); r = await ali.callRaw(bytesContract, "panic_js_null", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: null*/ ) ); r = await ali.callRaw(bytesContract, "panic_utf8_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: 水*/ ) ); r = await ali.callRaw(bytesContract, "panicUtf8_valid_utf8_sequence", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: 水*/ ) ); r = await ali.callRaw(bytesContract, "panicUtf8_invalid_utf8_sequence", ""); t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-8 sequence." ); }); test("utf8 string can be convert to Uint8Array correctly", async (t) => { const { bob, bytesContract } = t.context.accounts; let res = await bob.callRaw( bytesContract, "utf8_string_to_uint8array_tests", "" ); t.is(res.result.status.SuccessValue, ""); }); test("valid utf8 sequence can be convert to string correctly", async (t) => { const { bob, bytesContract } = t.context.accounts; let res = await bob.callRaw( bytesContract, "uint8array_to_utf8_string_tests", "" ); t.is(res.result.status.SuccessValue, ""); }); test("latin1 sequence can be convert to string correctly", async (t) => { const { bob, bytesContract } = t.context.accounts; let res = await bob.callRaw( bytesContract, "uint8array_to_latin1_string_tests", "" ); t.is(res.result.status.SuccessValue, ""); }); ================================================ FILE: tests/__tests__/constructor_validation.ava.js ================================================ import test from "ava"; import { execSync } from "child_process"; const BUILD_FAILURE_ERROR_CODE = 1; test("should not throw error, constructor is correctly initialized", async (t) => { t.notThrows(() => { execSync( "near-sdk-js build src/constructor-validation/all-parameters-set-in-constructor.ts build/all-parameters-set-in-constructor.wasm" ); }); }); test("should throw error, name is not inited", async (t) => { const error = t.throws(() => { execSync( "near-sdk-js build src/constructor-validation/1-parameter-not-set-in-constructor.ts build/1-parameter-not-set-in-constructor.wasm" ); }); t.is(error.status, BUILD_FAILURE_ERROR_CODE); }); test("should throw error, construcor is empty", async (t) => { const error = t.throws(() => { execSync( "near-sdk-js build src/constructor-validation/no-parameters-set-in-constructor.ts build/no-parameters-set-in-constructor.wasm" ); }); t.is(error.status, BUILD_FAILURE_ERROR_CODE); }); test("should throw error, construcor is not declared", async (t) => { const error = t.throws(() => { execSync( "near-sdk-js build src/constructor-validation/no-constructor.ts build/no-constructor.wasm" ); }); t.is(error.status, BUILD_FAILURE_ERROR_CODE); }); ================================================ FILE: tests/__tests__/decorators/migrate.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const counter = await root.devDeploy("./build/migrate.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, counter, ali }; }); // If the environment is reused, use test.after to replace test.afterEach test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("migration works", async (t) => { const { counter, ali } = t.context.accounts; const res1 = await counter.view("getCount", {}); t.is(res1, 0); await ali.call(counter, "increase", {}); const res2 = await counter.view("getCount", {}); t.is(res2, 1); const migrationRes = await ali.callRaw(counter, "migrFuncValueTo18", {}); t.is(JSON.stringify(migrationRes).includes("Count: 0"), true); const res3 = await counter.view("getCount", {}); t.is(res3, 18); }); ================================================ FILE: tests/__tests__/decorators/near_bindgen.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const reqireInitFalse = await root.devDeploy("build/require_init_false.wasm"); const reqireInitTrue = await root.devDeploy("build/require_init_true.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, reqireInitFalse, reqireInitTrue, ali, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Uninitialized contract throw error if requireInit = true", async (t) => { const { ali, reqireInitTrue } = t.context.accounts; const callResult = await ali.callRaw(reqireInitTrue, "setStatus", { status: "hello", }); t.assert( callResult.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Contract must be initialized" ) ); const err = await t.throwsAsync(() => reqireInitTrue.view("getStatus", {})); t.assert(err.message.includes("Contract must be initialized")); }); test("Uninitialized contract does not throw error if requireInit = false", async (t) => { const { ali, reqireInitFalse } = t.context.accounts; await ali.callRaw(reqireInitFalse, "setStatus", { status: "hello" }); t.is(await reqireInitFalse.view("getStatus", {}), "hello"); }); test("Init function panics if called more then once", async (t) => { const { ali, reqireInitTrue, reqireInitFalse } = t.context.accounts; await ali.call(reqireInitTrue, "init", { status: "hello" }); const res1 = await ali.callRaw(reqireInitTrue, "init", { status: "hello" }); t.assert( res1.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Contract already initialized" ) ); await ali.call(reqireInitFalse, "init", { status: "hello" }); const res2 = await ali.callRaw(reqireInitFalse, "init", { status: "hello" }); t.assert( res2.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Contract already initialized" ) ); }); ================================================ FILE: tests/__tests__/decorators/payable.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; const DEPOSIT = 1_000_000_000; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const payableContract = await root.devDeploy("build/payable.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, payableContract, ali, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("payable: true functions works with deposit", async (t) => { const { ali, payableContract } = t.context.accounts; await t.notThrowsAsync( ali.call( payableContract, "setValueWithPayableFunction", { value: "hello" }, { attachedDeposit: DEPOSIT } ) ); }); test("payable: true functions works without deposit", async (t) => { const { ali, payableContract } = t.context.accounts; await t.notThrowsAsync( ali.call(payableContract, "setValueWithPayableFunction", { value: "hello" }) ); }); test("payable: false throws if atach deposit", async (t) => { const { ali, payableContract } = t.context.accounts; const result = await ali.callRaw( payableContract, "setValueWithNotPayableFunction", { value: "hello" }, { attachedDeposit: DEPOSIT } ); t.assert( result.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Function is not payable" ) ); }); test("payable default throws if atach deposit", async (t) => { const { ali, payableContract } = t.context.accounts; const result = await ali.callRaw( payableContract, "setValueWithNotPayableFunctionByDefault", { value: "hello" }, { attachedDeposit: DEPOSIT } ); t.assert( result.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Function is not payable" ) ); }); test("payable default works without deposit", async (t) => { const { ali, payableContract } = t.context.accounts; await t.notThrowsAsync( ali.call(payableContract, "setValueWithNotPayableFunctionByDefault", { value: "hello", }) ); }); ================================================ FILE: tests/__tests__/decorators/private.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { const worker = await Worker.init(); const root = worker.rootAccount; const contract = await root.devDeploy("build/private.wasm"); const ali = await root.createSubAccount("ali"); t.context.worker = worker; t.context.accounts = { root, contract, ali, }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("private: true throws if called from another acc", async (t) => { const { ali, contract } = t.context.accounts; const result = await ali.callRaw(contract, "setValueWithPrivateFunction", { value: "hello", }); t.assert( result.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Function is private" ) ); }); test("private: true not throws if called from owner acc", async (t) => { const { contract } = t.context.accounts; await t.notThrowsAsync( contract.call(contract, "setValueWithNotPrivateFunction", { value: "hello", }) ); }); test("private: default not throws from another acc", async (t) => { const { ali, contract } = t.context.accounts; await t.notThrowsAsync( ali.call(contract, "setValueWithNotPrivateFunctionByDefault", { value: "hello", }) ); }); ================================================ FILE: tests/__tests__/function-params.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; const functionParamsContract = await root.createSubAccount( "function-params-contract" ); await functionParamsContract.deploy("build/function-params.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, functionParamsContract, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("get current account id correct", async (t) => { const { ali, functionParamsContract } = t.context.accounts; await ali.call(functionParamsContract, "set_values", { param1: "newVal1", param2: "newVal2", param3: "newVal3", }); let values = await functionParamsContract.view("get_values", ""); t.deepEqual(values, { val3: "newVal3", val2: "newVal2", val1: "newVal1" }); }); ================================================ FILE: tests/__tests__/lookup-map.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const lookupMapContract = await root.createSubAccount("lookup-map-contract"); await lookupMapContract.deploy("build/lookup-map.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lookupMapContract, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("LookupMap set() get()", async (t) => { const { ali, lookupMapContract } = t.context.accounts; t.is(await lookupMapContract.view("get", { key: "hello" }), null); t.is(await lookupMapContract.view("containsKey", { key: "hello" }), false); await ali.call(lookupMapContract, "set", { key: "hello", value: "world" }); t.is(await lookupMapContract.view("get", { key: "hello" }), "world"); t.is(await lookupMapContract.view("containsKey", { key: "hello" }), true); }); test("LookupMap update, remove", async (t) => { const { ali, lookupMapContract } = t.context.accounts; await ali.call(lookupMapContract, "set", { key: "hello", value: "world" }); await ali.call(lookupMapContract, "set", { key: "hello1", value: "world0" }); // update a value, len shouldn't change await ali.call(lookupMapContract, "set", { key: "hello1", value: "world1" }); // update should have effect t.is(await lookupMapContract.view("get", { key: "hello1" }), "world1"); // not update key should not changed t.is(await lookupMapContract.view("get", { key: "hello" }), "world"); // remove non existing element should not error await ali.call(lookupMapContract, "remove_key", { key: "hello3" }); // remove existing key should work await ali.call(lookupMapContract, "remove_key", { key: "hello1" }); t.is(await lookupMapContract.view("containsKey", { key: "hello1" }), false); // not removed key should not affected t.is(await lookupMapContract.view("get", { key: "hello" }), "world"); }); test("LookupMap extend", async (t) => { const { ali, lookupMapContract } = t.context.accounts; await ali.call(lookupMapContract, "extend", { kvs: [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ], }); t.is(await lookupMapContract.view("get", { key: "hello" }), "world"); t.is(await lookupMapContract.view("get", { key: "hello1" }), "world1"); t.is(await lookupMapContract.view("get", { key: "hello2" }), "world2"); }); test("LookupMap set get object", async (t) => { const { ali, lookupMapContract } = t.context.accounts; await ali.call(lookupMapContract, "add_house", {}); t.is( await lookupMapContract.view("get_house", {}), "house house1 has 2 rooms. room room1 is 200sqft." ); }); test("LookupMap allows you to use the same key for the second time", async (t) => { const { ali, lookupMapContract } = t.context.accounts; await ali.call(lookupMapContract, "set", { key: "hello", value: "world" }); await ali.call(lookupMapContract, "set", { key: "hello", value: "world" }); t.is(await lookupMapContract.view("get", { key: "hello" }), "world"); }); test.only("UTF-8 in arguments, store in collections & state, return in returns", async (t) => { const { ali, lookupMapContract } = t.context.accounts; let data = { utf8emoji: "😂", utf8char: "水", // this is the byte sequence of above utf8 char, it will be escaped in js contract // so it won't be mis-recorgnized as above utf8 char. utf8char_charcode_seq: "\xe6\xb0\xb4", // this and above shows arbitrary binary data can be put in arguments, state and return // default serialization and deserialization works latin1_charcode_seq: "\xc2\x00\x01\xff", }; let res = await ali.callRaw(lookupMapContract, "set", { key: "utf8test", value: data, }); t.is(res.result.status.SuccessValue, ""); t.deepEqual(await lookupMapContract.view("get", { key: "utf8test" }), data); }); ================================================ FILE: tests/__tests__/lookup-set.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const lookupSetContract = await root.createSubAccount("lookup-set-contract"); await lookupSetContract.deploy("build/lookup-set.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, lookupSetContract, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("LookupSet set() contains()", async (t) => { const { ali, lookupSetContract } = t.context.accounts; t.is(await lookupSetContract.view("contains", { key: "hello" }), false); await ali.call(lookupSetContract, "set", { key: "hello" }); t.is(await lookupSetContract.view("contains", { key: "hello" }), true); }); test("LookupSet remove", async (t) => { const { ali, lookupSetContract } = t.context.accounts; await ali.call(lookupSetContract, "set", { key: "hello" }); await ali.call(lookupSetContract, "set", { key: "hello1" }); // remove non existing element should not error await ali.call(lookupSetContract, "remove_key", { key: "hello3" }); // remove existing key should work await ali.call(lookupSetContract, "remove_key", { key: "hello1" }); t.is(await lookupSetContract.view("contains", { key: "hello1" }), false); // not removed key should not affected t.is(await lookupSetContract.view("contains", { key: "hello" }), true); }); test("LookupSet extend", async (t) => { const { ali, lookupSetContract } = t.context.accounts; await ali.call(lookupSetContract, "extend", { keys: ["hello", "world", "hello1"], }); t.is(await lookupSetContract.view("contains", { key: "hello" }), true); t.is(await lookupSetContract.view("contains", { key: "hello1" }), true); t.is(await lookupSetContract.view("contains", { key: "world" }), true); }); test("Add and check exist of object", async (t) => { const { ali, lookupSetContract } = t.context.accounts; let houseSpec = { name: "a", rooms: [{ name: "bedroom", size: "300sqft" }] }; t.is(await lookupSetContract.view("house_exist", houseSpec), false); await ali.call(lookupSetContract, "add_house", houseSpec); t.is(await lookupSetContract.view("house_exist", houseSpec), true); }); ================================================ FILE: tests/__tests__/test-bigint-serialization.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Create and deploy test contract const bsContract = await root.devDeploy("build/bigint-serialization.wasm"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, bsContract }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("get initial bigint field value", async (t) => { const { bsContract } = t.context.accounts; const bigintField = await bsContract.view("getBigintField"); t.is(bigintField, `${1n}`); }); test("get bigint field after increment", async (t) => { const { bsContract } = t.context.accounts; const bigintField = await bsContract.view("getBigintField"); t.is(bigintField, `${1n}`); await bsContract.call(bsContract, "increment", ""); const afterIncrement = await bsContract.view("getBigintField"); t.is(afterIncrement, `${2n}`); }); test("get bigint field after set", async (t) => { const { bsContract } = t.context.accounts; const bigintField = await bsContract.view("getBigintField"); t.is(bigintField, `${1n}`); await bsContract.call(bsContract, "setBigintField", { bigintField: `${3n}` }); const afterSet = await bsContract.view("getBigintField"); t.is(afterSet, `${3n}`); }); ================================================ FILE: tests/__tests__/test-date-serialization.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Create and deploy test contract const dsContract = await root.createSubAccount("ds-contract"); await dsContract.deploy("build/date-serialization.wasm"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, dsContract }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("get initial date field value", async (t) => { const { dsContract } = t.context.accounts; const dateField = await dsContract.view("getDateField"); t.is(dateField, new Date(0).toISOString()); }); test("get date field after set", async (t) => { const { dsContract } = t.context.accounts; const dateField = await dsContract.view("getDateField"); t.is(dateField, new Date(0).toISOString()); const newDate = new Date(); await dsContract.call(dsContract, "setDateField", { dateField: newDate }); const afterSet = await dsContract.view("getDateField"); t.is(afterSet, newDate.toISOString()); }); test("get date field in milliseconds", async (t) => { const { dsContract } = t.context.accounts; const dateField = await dsContract.view("getDateFieldAsMilliseconds"); t.is(dateField, new Date(0).getTime()); const newDate = new Date(); await dsContract.call(dsContract, "setDateField", { dateField: newDate }); const afterIncrement = await dsContract.view("getDateFieldAsMilliseconds"); t.is(afterIncrement, newDate.getTime()); }); ================================================ FILE: tests/__tests__/test-middlewares.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the contract. const middlewares = await root.createSubAccount("middlewares-contract"); await middlewares.deploy("build/middlewares.wasm"); // Create the init args. const args = JSON.stringify({ randomData: "anything" }); // Capture the result of the init function call. const result = await middlewares.callRaw(middlewares, "init", args); // Extract the logs. const { logs } = result.result.receipts_outcome[0].outcome; // Create the expected logs. const expectedLogs = [`Log from middleware: ${args}`]; // Check for correct logs. t.deepEqual(logs, expectedLogs); // Create test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, middlewares, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("The middleware logs with call functions", async (t) => { const { ali, middlewares } = t.context.accounts; // Create the arguments which will be passed to the function. const args = JSON.stringify({ id: "1", text: "hello" }); // Call the function. const result = await ali.callRaw(middlewares, "add", args); // Extract the logs. const { logs } = result.result.receipts_outcome[0].outcome; // Create the expected logs. const expectedLogs = [`Log from middleware: ${args}`]; t.deepEqual(logs, expectedLogs); }); test("The middleware logs with view functions", async (t) => { const { ali, middlewares } = t.context.accounts; // Create the arguments which will be passed to the function. const args = JSON.stringify({ id: "1", accountId: "hello" }); // Call the function. const result = await ali.callRaw(middlewares, "get", args); // Extract the logs. const { logs } = result.result.receipts_outcome[0].outcome; // Create the expected logs. const expectedLogs = [`Log from middleware: ${args}`]; t.deepEqual(logs, expectedLogs); }); test("The middleware logs with two middleware functions", async (t) => { const { ali, middlewares } = t.context.accounts; // Create the arguments which will be passed to the function. const args = JSON.stringify({ id: "1", accountId: "hello" }); // Call the function. const result = await ali.callRaw(middlewares, "get_two", args); // Extract the logs. const { logs } = result.result.receipts_outcome[0].outcome; // Create the expected logs. const expectedLogs = [`Log from middleware: ${args}`, "Second log!"]; t.deepEqual(logs, expectedLogs); }); test("The middleware logs with private functions", async (t) => { const { ali, middlewares } = t.context.accounts; // Create the arguments which will be passed to the function. const args = { id: "test", accountId: "tset" }; // Call the function. const result = await ali.callRaw(middlewares, "get_private", ""); // Extract the logs. const { logs } = result.result.receipts_outcome[0].outcome; // Create the expected logs. const expectedLogs = [`Log from middleware: ${args}`]; t.deepEqual(logs, expectedLogs); }); ================================================ FILE: tests/__tests__/test-public-key.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Create and deploy test contract const pkContract = await root.createSubAccount("pk"); await pkContract.deploy("build/public-key.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, pkContract, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("add signer key should success", async (t) => { const { ali, pkContract } = t.context.accounts; let r = await ali.callRaw(pkContract, "test_add_signer_key", ""); t.is(r.result.status.SuccessValue, ""); }); test("add ed25519 key bytes should success", async (t) => { const { ali, pkContract } = t.context.accounts; let r = await ali.callRaw(pkContract, "test_add_ed25519_key_bytes", ""); t.is(r.result.status.SuccessValue, ""); }); test("add ed25519 key string should success", async (t) => { const { ali, pkContract } = t.context.accounts; let r = await ali.callRaw(pkContract, "test_add_ed25519_key_string", ""); t.is(r.result.status.SuccessValue, ""); }); test("add secp256k1 key bytes should success", async (t) => { const { bob, pkContract } = t.context.accounts; let r = await bob.callRaw(pkContract, "test_add_secp256k1_key_bytes", ""); t.is(r.result.status.SuccessValue, ""); }); test("add secp256k1 key string should success", async (t) => { const { bob, pkContract } = t.context.accounts; let r = await bob.callRaw(pkContract, "test_add_secp256k1_key_string", "", { gas: "100 Tgas", }); t.is(r.result.status.SuccessValue, ""); }); test("add invalid key should error", async (t) => { const { bob, pkContract } = t.context.accounts; let r = await bob.callRaw(pkContract, "add_invalid_public_key", ""); t.is(r.result.status.SuccessValue, undefined); t.is( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError, "VM Logic provided an invalid public key" ); }); test("curve type check should success", async (t) => { const { carl, pkContract } = t.context.accounts; let r = await carl.callRaw(pkContract, "curve_type", ""); t.is(r.result.status.SuccessValue, ""); }); test("create invalid curve type should fail", async (t) => { const { carl, pkContract } = t.context.accounts; let r = await carl.callRaw(pkContract, "create_invalid_curve_type", ""); t.is(r.result.status.SuccessValue, undefined); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Unknown curve" ) ); }); test("create invalid length should fail", async (t) => { const { carl, pkContract } = t.context.accounts; let r = await carl.callRaw(pkContract, "create_invalid_length", ""); t.is(r.result.status.SuccessValue, undefined); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Invalid length" ) ); }); test("create invalid base58 should fail", async (t) => { const { carl, pkContract } = t.context.accounts; let r = await carl.callRaw(pkContract, "create_from_invalid_base58", ""); t.is(r.result.status.SuccessValue, undefined); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Base58 error" ) ); }); ================================================ FILE: tests/__tests__/test_alt_bn128_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const altBn128ApiContract = await root.devDeploy("build/alt_bn128_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, altBn128ApiContract, ali }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("test_alt_bn128_g1_sum", async (t) => { const { ali, altBn128ApiContract } = t.context.accounts; let r = await ali.callRaw(altBn128ApiContract, "test_alt_bn128_g1_sum", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 11, 49, 94, 29, 152, 111, 116, 138, 248, 2, 184, 8, 159, 80, 169, 45, 149, 48, 32, 49, 37, 6, 133, 105, 171, 194, 120, 44, 195, 17, 180, 35, 137, 154, 4, 192, 211, 244, 93, 200, 2, 44, 0, 64, 26, 108, 139, 147, 88, 235, 242, 23, 253, 52, 110, 236, 67, 99, 176, 2, 186, 198, 228, 25, ]) ); }); test("test_alt_bn128_g1_multiexp", async (t) => { const { ali, altBn128ApiContract } = t.context.accounts; let r = await ali.callRaw( altBn128ApiContract, "test_alt_bn128_g1_multiexp", "" ); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 150, 94, 159, 52, 239, 226, 181, 150, 77, 86, 90, 186, 102, 219, 243, 204, 36, 128, 164, 209, 106, 6, 62, 124, 235, 104, 223, 195, 30, 204, 42, 20, 13, 158, 14, 197, 133, 73, 43, 171, 28, 68, 82, 116, 244, 164, 36, 251, 244, 8, 234, 40, 118, 55, 216, 187, 242, 39, 213, 160, 192, 184, 28, 23, ]) ); }); test("test_alt_bn128_pairing_check", async (t) => { const { ali, altBn128ApiContract } = t.context.accounts; let r = await ali.call( altBn128ApiContract, "test_alt_bn128_pairing_check_valid", {} ); t.is(r, true); r = await ali.call( altBn128ApiContract, "test_alt_bn128_pairing_check_invalid", {} ); t.is(r, false); }); ================================================ FILE: tests/__tests__/test_context_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const contextApiContract = await root.devDeploy("build/context_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, contextApiContract, ali, bob, carl }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("get current account id correct", async (t) => { const { ali, contextApiContract } = t.context.accounts; let r = await ali.call(contextApiContract, "get_current_account_id", ""); t.is(r, contextApiContract.accountId); }); test("get signer account id correct", async (t) => { const { ali, contextApiContract } = t.context.accounts; let r = await ali.call(contextApiContract, "get_signer_account_id", ""); t.is(r, ali.accountId); }); test("get predecessor account id correct", async (t) => { const { ali, contextApiContract } = t.context.accounts; let r = await ali.call(contextApiContract, "get_predecessor_account_id", ""); t.is(r, ali.accountId); }); test("get signer account pk correct", async (t) => { const { ali, contextApiContract } = t.context.accounts; let r = await ali.callRaw(contextApiContract, "get_signer_account_pk", ""); // the prefixing byte 0 indicates it's a ED25519 PublicKey, see how PublicKey is serialized in nearcore t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.concat([ Buffer.from([0]), Buffer.from((await ali.getKey(ali.accountId)).getPublicKey().data), ]) ); }); test("get input correct", async (t) => { const { bob, contextApiContract } = t.context.accounts; let r = await bob.callRaw( contextApiContract, "get_input", new Uint8Array([0, 1, 255]) ); t.is( r.result.status.SuccessValue, Buffer.from(new Uint8Array([0, 1, 255])).toString("base64") ); }); test("get storage usage", async (t) => { const { carl, contextApiContract } = t.context.accounts; let r = await carl.call(contextApiContract, "get_storage_usage", "", { gas: "10 TGas", }); t.is(r > 0, true); }); test("get block height", async (t) => { const { bob, contextApiContract } = t.context.accounts; let r = await bob.call(contextApiContract, "get_block_height", ""); t.is(r > 0, true); }); test("get block timestamp", async (t) => { let time = new Date().getTime() * 1e6; const { bob, contextApiContract } = t.context.accounts; let r = await bob.call(contextApiContract, "get_block_timestamp", ""); t.is(r > time, true); }); test("get epoch height", async (t) => { const { bob, contextApiContract } = t.context.accounts; let r = await bob.call(contextApiContract, "get_epoch_height", ""); t.is(r, 1); }); test("get attached deposit", async (t) => { const { carl, contextApiContract } = t.context.accounts; let r = await carl.call(contextApiContract, "get_attached_deposit", "", { attachedDeposit: 3, }); t.is(r, "3"); for (let i = 1; i <= 10; i++) { // 1 NEAR, 2 NEAR, ..., 10 NEAR let r = await carl.call( contextApiContract, "get_attached_deposit", {}, { attachedDeposit: i.toString() + "000000000000000000000000" } ); t.is(r, i.toString() + "000000000000000000000000"); } }); test("get prepaid gas", async (t) => { const { carl, contextApiContract } = t.context.accounts; let r = await carl.call(contextApiContract, "get_prepaid_gas", "", { gas: "10 TGas", }); t.is(r, 10000000000000); }); test("get used gas", async (t) => { const { carl, contextApiContract } = t.context.accounts; let r = await carl.call(contextApiContract, "get_used_gas", "", { gas: "10 TGas", }); t.is(r > 0, true); t.is(r < 10000000000000, true); }); test("get random seed", async (t) => { const { carl, contextApiContract } = t.context.accounts; let r = await carl.callRaw(contextApiContract, "get_random_seed", ""); t.is(Buffer.from(r.result.status.SuccessValue, "base64").length, 32); }); test("get validator stake test", async (t) => { const { carl, contextApiContract, root } = t.context.accounts; let r = await carl.call(contextApiContract, "get_validator_stake", ""); t.is(r, 0); r = await root.callRaw(contextApiContract, "get_validator_stake", ""); t.is( Buffer.from(r.result.status.SuccessValue, "base64").toString("ascii"), "50000000000000000000000000000000" ); r = await contextApiContract.viewRaw("get_total_stake", ""); t.is( Buffer.from(r.result).toString("ascii"), "50000000000000000000000000000000" ); }); ================================================ FILE: tests/__tests__/test_highlevel_promise.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; const highlevelPromise = await root.createSubAccount("highlevel-promise", { initialBalance: "100100N", }); await highlevelPromise.deploy("build/highlevel-promise.wasm"); // Create and deploy callee contract const calleeContract = await root.createSubAccount("callee-contract"); await calleeContract.deploy("build/promise_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, highlevelPromise, ali, bob, carl, calleeContract, }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("highlevel promise create account, transfer", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw( highlevelPromise, "test_promise_batch_create_transfer", "", { gas: "100 Tgas" } ); t.is( r.result.receipts_outcome[1].outcome.executor_id, highlevelPromise.getSubAccount("a").accountId ); t.is(r.result.receipts_outcome[1].outcome.status.SuccessValue, ""); let balance = await highlevelPromise.getSubAccount("a").balance(); t.is(balance.total.toString(), "10000000000000000000000000"); }); test("highlevel promise stake", async (t) => { const { highlevelPromise } = t.context.accounts; await highlevelPromise.callRaw( highlevelPromise, "test_promise_batch_stake", "", { gas: "100 Tgas" } ); let balance = await highlevelPromise.balance(); t.is(balance.staked.toString(), "100000000000000000000000000000"); }); test("highlevel promise add full access key", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw( highlevelPromise, "test_promise_add_full_access_key", "", { gas: "100 Tgas" } ); t.is(r.result.status.SuccessValue, ""); }); test("highlevel promise add function call key", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw( highlevelPromise, "test_promise_add_function_call_access_key", "", { gas: "100 Tgas" } ); t.is(r.result.status.SuccessValue, ""); }); test("highlevel promise delete account", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw(highlevelPromise, "test_delete_account", "", { gas: "100 Tgas", }); t.is(r.result.status.SuccessValue, ""); t.is(await highlevelPromise.getSubAccount("e").exists(), false); }); test("cross contract call panic", async (t) => { const { ali, highlevelPromise } = t.context.accounts; let r = await ali.callRaw(highlevelPromise, "callee_panic", "", { gas: "70 Tgas", }); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Smart contract panicked: it just panic" ) ); }); test("before and after cross contract call panic", async (t) => { const { carl, highlevelPromise } = t.context.accounts; let r = await carl.callRaw( highlevelPromise, "before_and_after_callee_panic", "", { gas: "70 Tgas", } ); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Smart contract panicked: it just panic" ) ); // full transaction is revert, no log t.deepEqual(r.result.transaction_outcome.outcome.logs, []); }); test("cross contract call panic then callback another contract method", async (t) => { const { carl, highlevelPromise } = t.context.accounts; let r = await carl.callRaw(highlevelPromise, "callee_panic_then", "", { gas: "70 Tgas", }); // promise then will continue, even though the promise before promise.then failed t.is(r.result.status.SuccessValue, ""); let state = await highlevelPromise.viewStateRaw(); t.is(state.length, 4); }); test("cross contract call panic and cross contract call success then callback another contract method", async (t) => { const { carl, highlevelPromise, calleeContract } = t.context.accounts; let r = await carl.callRaw(highlevelPromise, "callee_panic_and", "", { gas: "100 Tgas", }); // promise `and` promise `then` continues, even though one of two promise and was failed. Entire transaction also success t.is(r.result.status.SuccessValue, ""); let state = await calleeContract.viewStateRaw(); t.is(state.length, 3); state = await highlevelPromise.viewStateRaw(); t.is(state.length, 4); }); test("cross contract call success then call a panic method", async (t) => { const { carl, highlevelPromise, calleeContract } = t.context.accounts; let r = await carl.callRaw( highlevelPromise, "callee_success_then_panic", "", { gas: "100 Tgas", } ); // the last promise fail, cause the transaction fail t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "Smart contract panicked: it just panic" ) ); // but the first success cross contract call won't revert, the state is persisted let state = await calleeContract.viewStateRaw(); t.is(state.length, 3); }); test("handling error in promise then", async (t) => { const { carl, highlevelPromise } = t.context.accounts; let r = await carl.callRaw( highlevelPromise, "handle_error_in_promise_then", "", { gas: "70 Tgas", } ); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "caught error in the callback: " ) ); }); test("handling error in promise then after promise and", async (t) => { const { carl, highlevelPromise } = t.context.accounts; let r = await carl.callRaw( highlevelPromise, "handle_error_in_promise_then_after_promise_and", "", { gas: "100 Tgas", } ); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( "caught error in the callback: " ) ); }); test("highlevel promise then", async (t) => { const { ali, highlevelPromise, calleeContract } = t.context.accounts; let r = await ali.callRaw(highlevelPromise, "test_promise_then", "", { gas: "70 Tgas", }); // call the callee t.is( r.result.receipts_outcome[1].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[1].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "abc", } ); // the callback scheduled by promise_then t.is( r.result.receipts_outcome[3].outcome.executor_id, highlevelPromise.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[3].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: highlevelPromise.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: '{"callbackArg1":"def"}', promiseResults: [ JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "abc", }), ], callbackArg1: "def", } ); }); test("highlevel promise and", async (t) => { const { ali, highlevelPromise, calleeContract } = t.context.accounts; let r = await ali.callRaw(highlevelPromise, "test_promise_and", "", { gas: "150 Tgas", }); // console.log(JSON.stringify(r, null, 2)) // promise and schedule to call the callee t.is( r.result.receipts_outcome[1].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[1].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "abc", } ); // promise and schedule to call the callee, with different args t.is( r.result.receipts_outcome[3].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[3].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "def", } ); // the callback scheduled by promise_then on the promise created by promise_and t.is( r.result.receipts_outcome[5].outcome.executor_id, highlevelPromise.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[5].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: highlevelPromise.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: '{"callbackArg1":"ghi"}', promiseResults: [ JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "abc", }), JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: highlevelPromise.accountId, input: "def", }), ], callbackArg1: "ghi", } ); }); test("highlevel promise not build and not return", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw(highlevelPromise, "not_return_not_build", "", { gas: "100 Tgas", }); try { let balance = await highlevelPromise.getSubAccount("b").balance(); } catch (e) { t.is(e.type, "AccountDoesNotExist"); } }); test("highlevel promise build and not return", async (t) => { const { bob, highlevelPromise } = t.context.accounts; let r = await bob.callRaw(highlevelPromise, "build_not_return", "", { gas: "100 Tgas", }); t.is( r.result.receipts_outcome[1].outcome.executor_id, highlevelPromise.getSubAccount("b").accountId ); t.is(r.result.receipts_outcome[1].outcome.status.SuccessValue, ""); let balance = await highlevelPromise.getSubAccount("b").balance(); t.is(balance.total.toString(), "10000000000000000000000000"); }); ================================================ FILE: tests/__tests__/test_log_panic_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const testContract = await root.devDeploy("build/log_panic_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, testContract, ali }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Log expected types work", async (t) => { const { ali, testContract } = t.context.accounts; let r = await ali.callRaw(testContract, "log_expected_input_tests", ""); t.deepEqual(r.result.receipts_outcome[0].outcome.logs, [ "abc", "水", "333", '{"0":0,"1":1,"2":255}', '{"0":230,"1":176,"2":180}', "水", "水", ]); }); test("Log invalid utf-8 sequence panic", async (t) => { const { ali, testContract } = t.context.accounts; let r = await ali.callRaw(testContract, "log_invalid_utf8_sequence_test", ""); // console.log(JSON.stringify(r, null, 2)) t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-8 sequence." ); }); test("Log invalid utf-16 sequence panic", async (t) => { const { ali, testContract } = t.context.accounts; let r = await ali.callRaw( testContract, "log_invalid_utf16_sequence_test", "" ); t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-16 sequence." ); }); test("panic tests", async (t) => { const { ali, testContract } = t.context.accounts; let r = await ali.callRaw(testContract, "panic_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /^Smart contract panicked:*/ ) ); r = await ali.callRaw(testContract, "panic_ascii_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /^Smart contract panicked: abc*/ ) ); r = await ali.callRaw(testContract, "panic_js_number", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /^Smart contract panicked: 356*/ ) ); r = await ali.callRaw(testContract, "panic_js_undefined", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /^Smart contract panicked:*/ ) ); r = await ali.callRaw(testContract, "panic_js_null", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /^Smart contract panicked: null*/ ) ); r = await ali.callRaw(testContract, "panic_utf8_test", ""); t.assert( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.match( /Smart contract panicked: 水*/ ) ); r = await ali.callRaw(testContract, "panicUtf8_valid_utf8_sequence", ""); t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "Smart contract panicked: 水" ); r = await ali.callRaw(testContract, "panicUtf8_invalid_utf8_sequence", ""); t.deepEqual( r.result.receipts_outcome[0].outcome.status.Failure.ActionError.kind .FunctionCallError.ExecutionError, "String encoding is bad UTF-8 sequence." ); }); ================================================ FILE: tests/__tests__/test_math_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const mathApiContract = await root.devDeploy("build/math_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, mathApiContract, ali }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("sha256", async (t) => { const { ali, mathApiContract } = t.context.accounts; let r = await ali.callRaw(mathApiContract, "test_sha256", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 18, 176, 115, 156, 45, 100, 241, 132, 180, 134, 77, 42, 105, 111, 199, 127, 118, 112, 92, 255, 88, 43, 83, 147, 122, 55, 26, 36, 42, 156, 160, 158, ]) ); }); test("keccak256", async (t) => { const { ali, mathApiContract } = t.context.accounts; let r = await ali.callRaw(mathApiContract, "test_keccak256", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 104, 110, 58, 122, 230, 181, 215, 145, 231, 229, 49, 162, 123, 167, 177, 58, 26, 142, 129, 173, 7, 37, 9, 26, 233, 115, 64, 102, 61, 85, 10, 159, ]) ); }); test("keccak512", async (t) => { const { ali, mathApiContract } = t.context.accounts; let r = await ali.callRaw(mathApiContract, "test_keccak512", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 55, 134, 96, 137, 168, 122, 187, 95, 67, 76, 18, 122, 146, 11, 225, 106, 117, 194, 154, 157, 48, 160, 90, 146, 104, 209, 118, 126, 222, 230, 200, 125, 48, 73, 197, 236, 123, 173, 192, 197, 90, 153, 167, 121, 100, 88, 209, 240, 137, 86, 239, 41, 87, 128, 219, 249, 136, 203, 220, 109, 46, 168, 234, 190, ]) ); }); test("ripemd160", async (t) => { const { ali, mathApiContract } = t.context.accounts; let r = await ali.callRaw(mathApiContract, "test_ripemd160", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 21, 102, 156, 115, 232, 3, 58, 215, 35, 84, 129, 30, 143, 86, 212, 104, 70, 97, 14, 225, ]) ); }); test("ecrecover", async (t) => { const { ali, mathApiContract } = t.context.accounts; let r = await ali.callRaw(mathApiContract, "test_ecrecover", ""); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([ 227, 45, 244, 40, 101, 233, 113, 53, 172, 251, 101, 243, 186, 231, 27, 220, 134, 244, 212, 145, 80, 173, 106, 68, 11, 111, 21, 135, 129, 9, 136, 10, 10, 43, 38, 103, 247, 231, 37, 206, 234, 112, 198, 115, 9, 59, 246, 118, 99, 224, 49, 38, 35, 200, 224, 145, 177, 60, 242, 192, 241, 30, 246, 82, ]) ); }); ================================================ FILE: tests/__tests__/test_promise_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Create and deploy caller contract const callerContract = await root.createSubAccount("caller-contract"); await callerContract.deploy("build/promise_api.wasm"); // Create and deploy callee contract const calleeContract = await root.createSubAccount("callee-contract"); await calleeContract.deploy("build/promise_api.wasm"); // Create and deploy caller2 contract const caller2Contract = await root.createSubAccount("caller2", { initialBalance: "100100N", }); await caller2Contract.deploy("build/promise_batch_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, callerContract, calleeContract, ali, bob, caller2Contract, }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("promise create", async (t) => { const { ali, callerContract, calleeContract } = t.context.accounts; // default is 30 TGas, sufficient when the callee contract method is trivial let r = await ali.callRaw(callerContract, "test_promise_create", "", { gas: "40 Tgas", }); t.is( r.result.receipts_outcome[1].outcome.executor_id, calleeContract.accountId ); t.deepEqual( Buffer.from( r.result.receipts_outcome[1].outcome.status.SuccessValue, "base64" ), Buffer.from( JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "abc", }) ) ); }); test("promise then", async (t) => { const { ali, callerContract, calleeContract } = t.context.accounts; let r = await ali.callRaw(callerContract, "test_promise_then", "", { gas: "70 Tgas", }); // console.log(JSON.stringify(r, null, 2)) // call the callee t.is( r.result.receipts_outcome[1].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[1].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "abc", } ); // the callback scheduled by promise_then t.is( r.result.receipts_outcome[3].outcome.executor_id, callerContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[3].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: callerContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "def", promiseResults: [ JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "abc", }), ], } ); }); test("promise and", async (t) => { const { ali, callerContract, calleeContract } = t.context.accounts; let r = await ali.callRaw(callerContract, "test_promise_and", "", { gas: "150 Tgas", }); // console.log(JSON.stringify(r, null, 2)) // promise and schedule to call the callee t.is( r.result.receipts_outcome[1].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[1].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "abc", } ); // promise and schedule to call the callee, with different args t.is( r.result.receipts_outcome[3].outcome.executor_id, calleeContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[3].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "def", } ); // the callback scheduled by promise_then on the promise created by promise_and t.is( r.result.receipts_outcome[5].outcome.executor_id, callerContract.accountId ); t.deepEqual( JSON.parse( Buffer.from( r.result.receipts_outcome[5].outcome.status.SuccessValue, "base64" ) ), { currentAccountId: callerContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "ghi", promiseResults: [ JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "abc", }), JSON.stringify({ currentAccountId: calleeContract.accountId, signerAccountId: ali.accountId, predecessorAccountId: callerContract.accountId, input: "def", }), ], } ); }); test("promise batch create account, transfer", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw( caller2Contract, "test_promise_batch_create_transfer", "", { gas: "100 Tgas" } ); t.is( r.result.receipts_outcome[1].outcome.executor_id, caller2Contract.getSubAccount("a").accountId ); t.is(r.result.receipts_outcome[1].outcome.status.SuccessValue, ""); let balance = await caller2Contract.getSubAccount("a").balance(); t.is(balance.total.toString(), "10000000000000000000000000"); }); test("promise batch deploy contract and call", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw( caller2Contract, "test_promise_batch_deploy_call", "", { gas: "300 Tgas" } ); let deployed = caller2Contract.getSubAccount("b"); t.deepEqual(JSON.parse(Buffer.from(r.result.status.SuccessValue, "base64")), { currentAccountId: deployed.accountId, signerAccountId: bob.accountId, predecessorAccountId: caller2Contract.accountId, input: "abc", }); }); test("promise batch stake", async (t) => { const { caller2Contract } = t.context.accounts; await caller2Contract.callRaw( caller2Contract, "test_promise_batch_stake", "", { gas: "100 Tgas" } ); let balance = await caller2Contract.balance(); t.is(balance.staked.toString(), "100000000000000000000000000000"); }); test("promise batch add full access key", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw( caller2Contract, "test_promise_add_full_access_key", "", { gas: "100 Tgas" } ); t.is(r.result.status.SuccessValue, ""); }); test("promise batch add function call key", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw( caller2Contract, "test_promise_add_function_call_access_key", "", { gas: "100 Tgas" } ); t.is(r.result.status.SuccessValue, ""); }); test("promise delete account", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw(caller2Contract, "test_delete_account", "", { gas: "100 Tgas", }); t.is(r.result.status.SuccessValue, ""); t.is(await caller2Contract.getSubAccount("e").exists(), false); }); test("promise batch function call weight", async (t) => { const { ali, caller2Contract } = t.context.accounts; let r = await ali.callRaw( caller2Contract, "test_promise_batch_call_weight", "", { gas: "100 Tgas" } ); t.assert(r.result.status.SuccessValue); }); test("promise batch transfer overflow", async (t) => { const { bob, caller2Contract } = t.context.accounts; let r = await bob.callRaw(caller2Contract, "test_transfer_overflow", "", { gas: "100 Tgas", }); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Expect Uint128 for amount" ) ); }); test("promise create gas overflow", async (t) => { const { ali, callerContract } = t.context.accounts; let r = await ali.callRaw( callerContract, "test_promise_create_gas_overflow", "", { gas: "100 Tgas" } ); t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.startsWith( "Smart contract panicked: Expect Uint64 for gas" ) ); }); ================================================ FILE: tests/__tests__/test_storage_api.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Use beforeEach instead of before to start from scratch state for each test // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const storageApiContract = await root.devDeploy("build/storage_api.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, storageApiContract, ali }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("storage read write", async (t) => { const { ali, storageApiContract } = t.context.accounts; let exist = await ali.call(storageApiContract, "test_storage_write", ""); t.is(exist, false); let r = await storageApiContract.viewRaw("test_storage_read", ""); t.deepEqual(r.result, [0, 1, 255]); exist = await ali.call(storageApiContract, "test_storage_write", ""); t.is(exist, true); }); test("storage remove", async (t) => { const { ali, storageApiContract } = t.context.accounts; let hasKey = await storageApiContract.view("test_storage_has_key", ""); t.is(hasKey, false); let exist = await ali.call(storageApiContract, "test_storage_remove", ""); t.is(exist, false); await ali.call(storageApiContract, "test_storage_write", ""); hasKey = await storageApiContract.view("test_storage_has_key", ""); t.is(hasKey, true); exist = await ali.call(storageApiContract, "test_storage_remove", ""); t.is(exist, true); }); test("storage get evicted", async (t) => { const { ali, storageApiContract } = t.context.accounts; let r = await ali.callRaw(storageApiContract, "test_storage_get_evicted", ""); console.log(JSON.stringify(r, null, 2)); t.deepEqual( Buffer.from(r.result.status.SuccessValue, "base64"), Buffer.from([0, 1, 255]) ); }); ================================================ FILE: tests/__tests__/typescript.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.before(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const typescriptContract = await root.devDeploy("build/typescript.wasm"); // Test users const ali = await root.createSubAccount("ali"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, typescriptContract, ali }; }); test.after.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("bigint", async (t) => { const { typescriptContract } = t.context.accounts; let r = await typescriptContract.view("bigint", ""); t.is(r, "3"); }); ================================================ FILE: tests/__tests__/unordered-map.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const unorderedMapContract = await root.createSubAccount( "unordered-map-contract" ); await unorderedMapContract.deploy("build/unordered-map.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, unorderedMapContract, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("UnorderedMap is empty by default", async (t) => { const { unorderedMapContract } = t.context.accounts; const result = await unorderedMapContract.view("len", {}); t.is(result, 0); }); test("UnorderedMap set() get()", async (t) => { const { ali, unorderedMapContract } = t.context.accounts; t.is(await unorderedMapContract.view("get", { key: "hello" }), null); await ali.call(unorderedMapContract, "set", { key: "hello", value: "world" }); t.is(await unorderedMapContract.view("get", { key: "hello" }), "world"); }); test("UnorderedMap insert, update, len and iterate", async (t) => { const { ali, unorderedMapContract } = t.context.accounts; t.is(await unorderedMapContract.view("len", {}), 0); t.deepEqual(await unorderedMapContract.view("toArray", {}), []); await ali.call(unorderedMapContract, "set", { key: "hello", value: "world" }); await ali.call(unorderedMapContract, "set", { key: "hello1", value: "world0", }); t.is(await unorderedMapContract.view("len", {}), 2); // update a value, len shouldn't change await ali.call(unorderedMapContract, "set", { key: "hello1", value: "world1", }); t.is(await unorderedMapContract.view("len", {}), 2); // update should have effect t.is(await unorderedMapContract.view("get", { key: "hello1" }), "world1"); await ali.call(unorderedMapContract, "set", { key: "hello2", value: "world2", }); t.is(await unorderedMapContract.view("len", {}), 3); // Try to set a key with same value, len shouldn't change await ali.call(unorderedMapContract, "set", { key: "hello2", value: "world2", }); t.is(await unorderedMapContract.view("len", {}), 3); t.deepEqual(await unorderedMapContract.view("toArray", {}), [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ]); }); test("UnorderedMap extend, remove, clear", async (t) => { const { ali, unorderedMapContract } = t.context.accounts; await ali.call(unorderedMapContract, "extend", { kvs: [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ], }); t.deepEqual(await unorderedMapContract.view("toArray", {}), [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ]); // remove non existing element should not error await ali.call(unorderedMapContract, "remove_key", { key: "hello3" }); t.deepEqual(await unorderedMapContract.view("toArray", {}), [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ]); // remove not the last one should work await ali.call(unorderedMapContract, "remove_key", { key: "hello" }); t.deepEqual(await unorderedMapContract.view("toArray", {}), [ ["hello2", "world2"], ["hello1", "world1"], ]); // remove the last one should work await ali.call(unorderedMapContract, "remove_key", { key: "hello1" }); t.deepEqual(await unorderedMapContract.view("toArray", {}), [ ["hello2", "world2"], ]); // remove when length is 1 should work t.is(await unorderedMapContract.view("len", {}), 1); t.is(await unorderedMapContract.view("isEmpty", {}), false); await ali.call(unorderedMapContract, "remove_key", { key: "hello2" }); t.deepEqual(await unorderedMapContract.view("toArray", {}), []); t.is(await unorderedMapContract.view("isEmpty", {}), true); await ali.call(unorderedMapContract, "extend", { kvs: [ ["hello", "world"], ["hello1", "world1"], ["hello2", "world2"], ], }); t.is(await unorderedMapContract.view("isEmpty", {}), false); await ali.call(unorderedMapContract, "clear", {}); t.deepEqual(await unorderedMapContract.view("toArray", {}), []); t.is(await unorderedMapContract.view("isEmpty", {}), true); }); test("UnorderedMap set get object", async (t) => { const { ali, unorderedMapContract } = t.context.accounts; await ali.call(unorderedMapContract, "add_house", {}); t.is( await unorderedMapContract.view("get_house", {}), "house house1 has 2 rooms. room room1 is 200sqft." ); }); test("UnorderedMap enumeration and pagination of keys", async (t) => { const { bob, unorderedMapContract } = t.context.accounts; await bob.call(unorderedMapContract, "extend", { kvs: [ ["aaa", "world"], ["bbb", "world1"], ["ccc", "world2"], ["ddd", "world3"], ], }); t.deepEqual(await unorderedMapContract.view("keys", {}), [ "aaa", "bbb", "ccc", "ddd", ]); t.deepEqual(await unorderedMapContract.view("keys", { start: 1 }), [ "bbb", "ccc", "ddd", ]); t.deepEqual(await unorderedMapContract.view("keys", { limit: 2 }), [ "aaa", "bbb", ]); t.deepEqual(await unorderedMapContract.view("keys", { start: 1, limit: 2 }), [ "bbb", "ccc", ]); }); ================================================ FILE: tests/__tests__/unordered-set.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const unorderedSetContract = await root.createSubAccount( "unordered-set-contract" ); await unorderedSetContract.deploy("build/unordered-set.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, unorderedSetContract, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("UnorderedSet is empty by default", async (t) => { const { unorderedSetContract } = t.context.accounts; const result = await unorderedSetContract.view("len", {}); t.is(result, 0); t.is(await unorderedSetContract.view("isEmpty", {}), true); }); test("UnorderedSet set() contains()", async (t) => { const { ali, unorderedSetContract } = t.context.accounts; t.is( await unorderedSetContract.view("contains", { element: "hello" }), false ); await ali.call(unorderedSetContract, "set", { element: "hello" }); t.is(await unorderedSetContract.view("contains", { element: "hello" }), true); }); test("UnorderedSet insert, len and iterate", async (t) => { const { ali, unorderedSetContract } = t.context.accounts; t.is(await unorderedSetContract.view("len", {}), 0); t.deepEqual(await unorderedSetContract.view("toArray", {}), []); await ali.call(unorderedSetContract, "set", { element: "hello" }); t.is(await unorderedSetContract.view("len", {}), 1); await ali.call(unorderedSetContract, "set", { element: "hello1" }); t.is(await unorderedSetContract.view("len", {}), 2); // insert the same value, len shouldn't change await ali.call(unorderedSetContract, "set", { element: "hello1" }); t.is(await unorderedSetContract.view("len", {}), 2); t.deepEqual(await unorderedSetContract.view("toArray", {}), [ "hello", "hello1", ]); }); test("UnorderedSet extend, remove, clear", async (t) => { const { ali, unorderedSetContract } = t.context.accounts; await ali.callRaw(unorderedSetContract, "extend", { elements: ["hello", "world", "hello1"], }); t.deepEqual(await unorderedSetContract.view("toArray", {}), [ "hello", "world", "hello1", ]); // remove non existing element should not error await ali.call(unorderedSetContract, "remove_key", { element: "hello3" }); t.deepEqual(await unorderedSetContract.view("toArray", {}), [ "hello", "world", "hello1", ]); // remove not the last one should work await ali.call(unorderedSetContract, "remove_key", { element: "hello" }); t.deepEqual(await unorderedSetContract.view("toArray", {}), [ "hello1", "world", ]); // remove the last one should work await ali.call(unorderedSetContract, "remove_key", { element: "world" }); t.deepEqual(await unorderedSetContract.view("toArray", {}), ["hello1"]); // remove when length is 1 should work t.is(await unorderedSetContract.view("len", {}), 1); t.is(await unorderedSetContract.view("isEmpty", {}), false); await ali.call(unorderedSetContract, "remove_key", { element: "hello1" }); t.deepEqual(await unorderedSetContract.view("toArray", {}), []); t.is(await unorderedSetContract.view("isEmpty", {}), true); await ali.call(unorderedSetContract, "extend", { elements: ["hello", "world", "hello1"], }); t.deepEqual(await unorderedSetContract.view("toArray", {}), [ "hello", "world", "hello1", ]); t.is(await unorderedSetContract.view("isEmpty", {}), false); // clear should work await ali.call(unorderedSetContract, "clear", {}); t.deepEqual(await unorderedSetContract.view("toArray", {}), []); t.is(await unorderedSetContract.view("isEmpty", {}), true); }); test("Add and check exist of object", async (t) => { const { ali, unorderedSetContract } = t.context.accounts; let houseSpec = { name: "a", rooms: [{ name: "bedroom", size: "300sqft" }] }; t.is(await unorderedSetContract.view("house_exist", houseSpec), false); await ali.call(unorderedSetContract, "add_house", houseSpec); t.is(await unorderedSetContract.view("house_exist", houseSpec), true); }); test("Enumerate and pagination of elements", async (t) => { const { bob, unorderedSetContract } = t.context.accounts; await bob.callRaw(unorderedSetContract, "extend", { elements: ["aaa", "bbb", "ccc", "ddd"], }); t.deepEqual(await unorderedSetContract.view("elements", {}), [ "aaa", "bbb", "ccc", "ddd", ]); t.deepEqual(await unorderedSetContract.view("elements", { start: 1 }), [ "bbb", "ccc", "ddd", ]); t.deepEqual(await unorderedSetContract.view("elements", { limit: 2 }), [ "aaa", "bbb", ]); t.deepEqual( await unorderedSetContract.view("elements", { start: 1, limit: 2 }), ["bbb", "ccc"] ); }); ================================================ FILE: tests/__tests__/vector.ava.js ================================================ import { Worker } from "near-workspaces"; import test from "ava"; test.beforeEach(async (t) => { // Init the worker and start a Sandbox server const worker = await Worker.init(); // Prepare sandbox for tests, create accounts, deploy contracts, etx. const root = worker.rootAccount; // Deploy the test contract. const vectorContract = await root.devDeploy("build/vector.wasm"); // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; t.context.accounts = { root, vectorContract, ali, bob, carl }; }); test.afterEach.always(async (t) => { await t.context.worker.tearDown().catch((error) => { console.log("Failed to tear down the worker:", error); }); }); test("Vector is empty by default", async (t) => { const { vectorContract } = t.context.accounts; let result = await vectorContract.view("len", {}); t.is(result, 0); t.is(await vectorContract.view("isEmpty", {}), true); }); test("Vector push, get, pop, replace", async (t) => { const { ali, vectorContract } = t.context.accounts; await ali.call(vectorContract, "push", { value: "hello" }); await ali.call(vectorContract, "push", { value: "world" }); await ali.call(vectorContract, "push", { value: "aaa" }); let result = await vectorContract.view("len", {}); t.is(result, 3); t.is(await vectorContract.view("get", { index: 0 }), "hello"); t.is(await vectorContract.view("get", { index: 2 }), "aaa"); t.is(await vectorContract.view("get", { index: 3 }), null); await ali.call(vectorContract, "pop", {}); (result = await vectorContract.view("len", {})), t.is(result, 2); t.is(await vectorContract.view("get", { index: 2 }), null); t.is(await vectorContract.view("get", { index: 1 }), "world"); await ali.call(vectorContract, "replace", { index: 1, value: "aaa" }); t.is(await vectorContract.view("get", { index: 1 }), "aaa"); }); test("Vector extend, toArray, swapRemove, clear", async (t) => { const { ali, vectorContract } = t.context.accounts; await ali.call(vectorContract, "extend", { kvs: ["hello", "world", "aaa"] }); t.deepEqual(await vectorContract.view("toArray", {}), [ "hello", "world", "aaa", ]); // swapRemove non existing element should error const error1 = await t.throwsAsync(() => ali.call(vectorContract, "swapRemove", { index: 3 }) ); t.assert(error1.message.includes(`Index out of bounds`)); t.deepEqual(await vectorContract.view("toArray", {}), [ "hello", "world", "aaa", ]); // swapRemove not the last one should work await ali.call(vectorContract, "swapRemove", { index: 0 }); t.deepEqual(await vectorContract.view("toArray", {}), ["aaa", "world"]); // swapRemove the last one should work await ali.call(vectorContract, "swapRemove", { index: 1 }); t.deepEqual(await vectorContract.view("toArray", {}), ["aaa"]); // swapRemove when length is 1 should work t.is(await vectorContract.view("len", {}), 1); t.is(await vectorContract.view("isEmpty", {}), false); await ali.call(vectorContract, "swapRemove", { index: 0 }); t.deepEqual(await vectorContract.view("toArray", {}), []); t.is(await vectorContract.view("isEmpty", {}), true); await ali.call(vectorContract, "extend", { kvs: ["hello", "world", "aaa"] }); t.is(await vectorContract.view("isEmpty", {}), false); await ali.call(vectorContract, "clear", {}); t.deepEqual(await vectorContract.view("toArray", {}), []); t.is(await vectorContract.view("isEmpty", {}), true); }); test("Vector add and get object", async (t) => { const { ali, vectorContract } = t.context.accounts; await ali.call(vectorContract, "add_house", {}); let result = await vectorContract.view("get_house", {}); t.deepEqual(result, { name: "house1", rooms: [ { name: "room1", size: "200sqft", }, { name: "room2", size: "300sqft", }, ], }); }); ================================================ FILE: tests/ava.config.cjs ================================================ require("util").inspect.defaultOptions.depth = 5; // Increase AVA's printing depth module.exports = { timeout: "300000", files: ["**/*.ava.js"], failWithoutAssertions: false, extensions: ["js"], }; ================================================ FILE: tests/jsconfig.json ================================================ { "compilerOptions": { "experimentalDecorators": true }, "exclude": ["node_modules"] } ================================================ FILE: tests/package.json ================================================ { "name": "tests", "version": "1.0.0", "description": "near-sdk-js tests", "main": "index.js", "type": "module", "scripts": { "build": "run-s build:*", "build:context-api": "near-sdk-js build src/context_api.js build/context_api.wasm", "build:math-api": "near-sdk-js build src/math_api.js build/math_api.wasm", "build:storage-api": "near-sdk-js build src/storage_api.js build/storage_api.wasm", "build:log-panic-api": "near-sdk-js build src/log_panic_api.js build/log_panic_api.wasm", "build:promise-api": "near-sdk-js build src/promise_api.js build/promise_api.wasm", "build:promise-batch-api": "near-sdk-js build src/promise_batch_api.js build/promise_batch_api.wasm", "build:highlevel-promise": "near-sdk-js build src/highlevel-promise.js build/highlevel-promise.wasm", "build:function-params": "near-sdk-js build src/function-params.js build/function-params.wasm", "build:lookup-map": "near-sdk-js build src/lookup-map.js build/lookup-map.wasm", "build:lookup-set": "near-sdk-js build src/lookup-set.js build/lookup-set.wasm", "build:unordered-map": "near-sdk-js build src/unordered-map.js build/unordered-map.wasm", "build:unordered-set": "near-sdk-js build src/unordered-set.js build/unordered-set.wasm", "build:vector": "near-sdk-js build src/vector.js build/vector.wasm", "build:bytes": "near-sdk-js build src/bytes.js build/bytes.wasm", "build:typescript": "near-sdk-js build src/typescript.ts build/typescript.wasm", "build:public-key": "near-sdk-js build src/public-key.js build/public-key.wasm", "build:near-bindgen": "near-sdk-js build src/decorators/require_init_true.ts build/require_init_true.wasm && near-sdk-js build src/decorators/require_init_false.ts build/require_init_false.wasm", "build:payable": "near-sdk-js build src/decorators/payable.ts build/payable.wasm", "build:private": "near-sdk-js build src/decorators/private.ts build/private.wasm", "build:bigint-serialization": "near-sdk-js build src/bigint-serialization.ts build/bigint-serialization.wasm", "build:date-serialization": "near-sdk-js build src/date-serialization.ts build/date-serialization.wasm", "build:middlewares": "near-sdk-js build src/middlewares.ts build/middlewares.wasm", "build:migrate": "near-sdk-js build src/migrate.ts build/migrate.wasm", "build:alt-bn128-api": "near-sdk-js build src/alt_bn128_api.js build/alt_bn128_api.wasm", "test": "ava", "test:context-api": "ava __tests__/test_context_api.ava.js", "test:math-api": "ava __tests__/test_math_api.ava.js", "test:storage-api": "ava __tests__/test_storage_api.ava.js", "test:log-panic-api": "ava __tests__/test_log_panic_api.ava.js", "test:promise-api": "ava __tests__/test_promise_api.ava.js", "test:highlevel-promise": "ava __tests__/test_highlevel_promise.ava.js", "test:function-params": "ava __tests__/function-params.ava.js", "test:lookup-map": "ava __tests__/lookup-map.ava.js", "test:lookup-set": "ava __tests__/lookup-set.ava.js", "test:unordered-set": "ava __tests__/unordered-set.ava.js", "test:unordered-map": "ava __tests__/unordered-map.ava.js", "test:vector": "ava __tests__/vector.ava.js", "test:bytes": "ava __tests__/bytes.ava.js", "test:typescript": "ava __tests__/typescript.ava.js", "test:public-key": "ava __tests__/test-public-key.ava.js", "test:near-bindgen": "ava __tests__/decorators/near_bindgen.ava.js", "test:payable": "ava __tests__/decorators/payable.ava.js", "test:private": "ava __tests__/decorators/private.ava.js", "test:bigint-serialization": "ava __tests__/test-bigint-serialization.ava.js", "test:date-serialization": "ava __tests__/test-date-serialization.ava.js", "test:serialization": "ava __tests__/test-serialization.ava.js", "test:constructor-validation": "ava __tests__/constructor_validation.ava.js", "test:migrate": "ava __tests__/decorators/migrate.ava.js", "test:middlewares": "ava __tests__/test-middlewares.ava.js", "test:abi": "ava __tests__/abi/abi.ava.js", "test:alt-bn128-api": "ava __tests__/test_alt_bn128_api.ava.js" }, "author": "Near Inc ", "license": "Apache-2.0", "devDependencies": { "ava": "4.3.3", "near-workspaces": "4.0.0", "npm-run-all": "4.1.5" }, "dependencies": { "typescript": "4.7.4", "near-sdk-js": "workspace:*" } } ================================================ FILE: tests/src/alt_bn128_api.js ================================================ import { near, bytes } from "near-sdk-js"; // Functions consumed by the altBn128PairingCheck tests export function test_alt_bn128_g1_sum() { // Originated from https://github.com/near/nearcore/blob/8cd095ffc98a6507ed2d2a8982a6a3e42ebc1b62/runtime/near-test-contracts/estimator-contract/src/lib.rs#L557-L720 let buffer = new Uint8Array([ 0, 11, 49, 94, 29, 152, 111, 116, 138, 248, 2, 184, 8, 159, 80, 169, 45, 149, 48, 32, 49, 37, 6, 133, 105, 171, 194, 120, 44, 195, 17, 180, 35, 137, 154, 4, 192, 211, 244, 93, 200, 2, 44, 0, 64, 26, 108, 139, 147, 88, 235, 242, 23, 253, 52, 110, 236, 67, 99, 176, 2, 186, 198, 228, 25, ]); near.valueReturnRaw(near.altBn128G1Sum(buffer)); } export function test_alt_bn128_g1_multiexp() { // Originated from https://github.com/near/nearcore/blob/8cd095ffc98a6507ed2d2a8982a6a3e42ebc1b62/runtime/near-test-contracts/estimator-contract/src/lib.rs#L557-L720 let buffer = new Uint8Array([ 16, 238, 91, 161, 241, 22, 172, 158, 138, 252, 202, 212, 136, 37, 110, 231, 118, 220, 8, 45, 14, 153, 125, 217, 227, 87, 238, 238, 31, 138, 226, 8, 238, 185, 12, 155, 93, 126, 144, 248, 200, 177, 46, 245, 40, 162, 169, 80, 150, 211, 157, 13, 10, 36, 44, 232, 173, 32, 32, 115, 123, 2, 9, 47, 190, 148, 181, 91, 69, 6, 83, 40, 65, 222, 251, 70, 81, 73, 60, 142, 130, 217, 176, 20, 69, 75, 40, 167, 41, 180, 244, 5, 142, 215, 135, 35, ]); near.valueReturnRaw(near.altBn128G1Multiexp(buffer)); } export function test_alt_bn128_pairing_check_valid() { // Taken from https://github.com/near/nearcore/blob/8cd095ffc98a6507ed2d2a8982a6a3e42ebc1b62/runtime/near-vm-runner/src/logic/tests/alt_bn128.rs#L239-L250 let valid_pair = new Uint8Array([ 117, 10, 217, 99, 113, 78, 234, 67, 183, 90, 26, 58, 200, 86, 195, 123, 42, 184, 213, 88, 224, 248, 18, 200, 108, 6, 181, 6, 28, 17, 99, 7, 36, 134, 53, 115, 192, 180, 3, 113, 76, 227, 174, 147, 50, 174, 79, 74, 151, 195, 172, 10, 211, 210, 26, 92, 117, 246, 65, 237, 168, 104, 16, 4, 1, 26, 3, 219, 6, 13, 193, 115, 77, 230, 27, 13, 242, 214, 195, 9, 213, 99, 135, 12, 160, 202, 114, 135, 175, 42, 116, 172, 79, 234, 26, 41, 212, 111, 192, 129, 124, 112, 57, 107, 38, 244, 230, 222, 240, 36, 65, 238, 133, 188, 19, 43, 148, 59, 205, 40, 161, 179, 173, 228, 88, 169, 231, 29, 17, 67, 163, 51, 165, 187, 101, 44, 250, 24, 68, 101, 92, 128, 203, 190, 51, 85, 9, 43, 58, 136, 68, 180, 92, 110, 185, 168, 107, 129, 45, 30, 187, 22, 100, 17, 75, 93, 216, 125, 23, 212, 11, 186, 199, 204, 1, 140, 133, 11, 82, 44, 65, 222, 20, 26, 48, 26, 132, 220, 25, 213, 93, 25, 79, 176, 4, 149, 151, 243, 11, 131, 253, 233, 121, 38, 222, 15, 118, 117, 200, 214, 175, 233, 130, 181, 193, 167, 255, 153, 169, 240, 207, 235, 28, 31, 83, 74, 69, 179, 6, 150, 72, 67, 74, 166, 130, 83, 82, 115, 123, 111, 208, 221, 64, 43, 237, 213, 186, 235, 7, 56, 251, 179, 95, 233, 159, 23, 109, 173, 85, 103, 8, 165, 235, 226, 218, 79, 72, 120, 172, 251, 20, 83, 121, 201, 140, 98, 170, 246, 121, 218, 19, 115, 42, 135, 60, 239, 30, 32, 49, 170, 171, 204, 196, 197, 160, 158, 168, 47, 23, 110, 139, 123, 222, 222, 245, 98, 125, 208, 70, 39, 110, 186, 146, 254, 66, 185, 118, 3, 78, 32, 47, 179, 197, 93, 79, 240, 204, 78, 236, 133, 213, 173, 117, 94, 63, 154, 68, 89, 236, 138, 0, 247, 242, 212, 245, 33, 249, 0, 35, 246, 233, 0, 124, 86, 198, 162, 201, 54, 19, 26, 196, 75, 254, 71, 70, 238, 51, 2, 23, 185, 152, 139, 134, 65, 107, 129, 114, 244, 47, 251, 240, 80, 193, 23, ]); near.valueReturn(near.altBn128PairingCheck(valid_pair)); } export function test_alt_bn128_pairing_check_invalid() { // Taken from https://github.com/near/nearcore/blob/8cd095ffc98a6507ed2d2a8982a6a3e42ebc1b62/runtime/near-vm-runner/src/logic/tests/alt_bn128.rs#L254-L265 let invalid_pair = new Uint8Array([ 117, 10, 217, 99, 113, 78, 234, 67, 183, 90, 26, 58, 200, 86, 195, 123, 42, 184, 213, 88, 224, 248, 18, 200, 108, 6, 181, 6, 28, 17, 99, 7, 36, 134, 53, 115, 192, 180, 3, 113, 76, 227, 174, 147, 50, 174, 79, 74, 151, 195, 172, 10, 211, 210, 26, 92, 117, 246, 65, 237, 168, 104, 16, 4, 1, 26, 3, 219, 6, 13, 193, 115, 77, 230, 27, 13, 242, 214, 195, 9, 213, 99, 135, 12, 160, 202, 114, 135, 175, 42, 116, 172, 79, 234, 26, 41, 212, 111, 192, 129, 124, 112, 57, 107, 38, 244, 230, 222, 240, 36, 65, 238, 133, 188, 19, 43, 148, 59, 205, 40, 161, 179, 173, 228, 88, 169, 231, 29, 17, 67, 163, 51, 165, 187, 101, 44, 250, 24, 68, 101, 92, 128, 203, 190, 51, 85, 9, 43, 58, 136, 68, 180, 92, 110, 185, 168, 107, 129, 45, 30, 187, 22, 100, 17, 75, 93, 216, 125, 23, 212, 11, 186, 199, 204, 1, 140, 133, 11, 82, 44, 65, 222, 20, 26, 48, 26, 132, 220, 25, 213, 93, 25, 117, 10, 217, 99, 113, 78, 234, 67, 183, 90, 26, 58, 200, 86, 195, 123, 42, 184, 213, 88, 224, 248, 18, 200, 108, 6, 181, 6, 28, 17, 99, 7, 36, 134, 53, 115, 192, 180, 3, 113, 76, 227, 174, 147, 50, 174, 79, 74, 151, 195, 172, 10, 211, 210, 26, 92, 117, 246, 65, 237, 168, 104, 16, 4, 109, 173, 85, 103, 8, 165, 235, 226, 218, 79, 72, 120, 172, 251, 20, 83, 121, 201, 140, 98, 170, 246, 121, 218, 19, 115, 42, 135, 60, 239, 30, 32, 49, 170, 171, 204, 196, 197, 160, 158, 168, 47, 23, 110, 139, 123, 222, 222, 245, 98, 125, 208, 70, 39, 110, 186, 146, 254, 66, 185, 118, 3, 78, 32, 47, 179, 197, 93, 79, 240, 204, 78, 236, 133, 213, 173, 117, 94, 63, 154, 68, 89, 236, 138, 0, 247, 242, 212, 245, 33, 249, 0, 35, 246, 233, 0, 124, 86, 198, 162, 201, 54, 19, 26, 196, 75, 254, 71, 70, 238, 51, 2, 23, 185, 152, 139, 134, 65, 107, 129, 114, 244, 47, 251, 240, 80, 193, 23, ]); near.valueReturn(near.altBn128PairingCheck(invalid_pair)); } ================================================ FILE: tests/src/bigint-serialization.ts ================================================ import { near, NearBindgen, call, view } from "near-sdk-js"; /** * Simple class used for testing of the `bigintField`. * - Includes methods: * - `getBigintField()` - returns the current `bigintField` value. * - `setBigintField(args: { bigintField })` - used to change the current `bigintField` value. * - `increment()` - increases the `bigintField` value by `1n`. * @param bigintField - Simple bigint used for testing. */ @NearBindgen({}) export class BigIntSerializationTest { bigintField: bigint; constructor() { this.bigintField = 1n; } @view({}) getBigintField(): bigint { near.log(`getBigintField: ${this.bigintField}`); return this.bigintField; } @call({}) setBigintField(args: { bigintField: bigint }): void { const bigintField = BigInt(args.bigintField); near.log(`setBigintField: ${bigintField}`); this.bigintField = bigintField; } @call({}) increment(): void { this.bigintField += 1n; near.log(`increment: ${this.bigintField}`); } } ================================================ FILE: tests/src/bytes.js ================================================ import { near, bytes, str, encode, decode, assert } from "near-sdk-js"; // Functions consumed by the bytes tests export function log_expected_input_tests() { // log ascii string near.log("abc"); // log string with utf-8 chars near.log("水"); // Buffer([0xe6, 0xb0, 0xb4]) // log number near.log(333); // log aribrary byte sequence near.log("\x00\x01\xff"); // log valid utf8 seqence near.log("\xe6\xb0\xb4"); // log valid utf8 sequence near.logUtf8(bytes("\xe6\xb0\xb4")); // log valid utf16 sequence near.logUtf16(bytes("\x34\x6c")); } export function log_unexpected_input_tests() { // log non-bytes with logUtf8 near.logUtf8("水"); // log non-bytes with logUtf16 near.logUtf16("水"); } export function log_invalid_utf8_sequence_test() { near.logUtf8(bytes("\x00\x01\xff")); } export function log_invalid_utf16_sequence_test() { near.logUtf16(bytes("\x00\x01\xff")); } export function storage_write_bytes() { near.storageWriteRaw(bytes("abc"), bytes("def")); near.storageWriteRaw(bytes("\x00\x01\xff"), bytes("\xe6\xb0\xb4")); near.storageWriteRaw(bytes("\xe6\xb0\xb4"), bytes("\x00ab")); } export function storage_write_utf8() { near.storageWrite("水", "😂"); } export function storage_read_utf8() { near.valueReturn(near.storageRead("水")); } export function storage_read_ascii_bytes() { near.valueReturn(near.storageRead("abc")); } export function storage_read_arbitrary_bytes_key_utf8_sequence_bytes_value() { near.valueReturnRaw(near.storageReadRaw(bytes("\x00\x01\xff"))); } export function storage_read_utf8_sequence_bytes_key_arbitrary_bytes_value() { near.valueReturnRaw(near.storageReadRaw(bytes("\xe6\xb0\xb4"))); } export function panic_test() { throw Error(); } export function panic_ascii_test() { throw Error("abc"); } export function panic_js_number() { throw Error(356); } export function panic_js_undefined() { throw Error(undefined); } export function panic_js_null() { throw Error(null); } export function panic_utf8_test() { throw Error("水"); } export function panicUtf8_valid_utf8_sequence() { near.panicUtf8(bytes("\xe6\xb0\xb4")); } export function panicUtf8_invalid_utf8_sequence() { near.panicUtf8(bytes("\x00\x01\xff")); } const areEqual = (first, second) => first.length === second.length && first.every((value, index) => value === second[index]); export function utf8_string_to_uint8array_tests() { let utf8chars = "水😂"; let withUtf8CharCodeSeq = "\xe6\xb0\xb4"; let withArbitraryLatinSeq = "\x00\x01\xff"; assert( areEqual( encode(utf8chars), new Uint8Array([230, 176, 180, 240, 159, 152, 130]) ) ); assert( areEqual( encode(withUtf8CharCodeSeq), new Uint8Array([195, 166, 194, 176, 194, 180]) ) ); assert( areEqual(encode(withArbitraryLatinSeq), new Uint8Array([0, 1, 195, 191])) ); assert(decode(encode(utf8chars)) == utf8chars); assert(decode(encode(withUtf8CharCodeSeq)) == withUtf8CharCodeSeq); assert(decode(encode(withArbitraryLatinSeq)) == withArbitraryLatinSeq); } export function uint8array_to_utf8_string_tests() { let validUtf8SeqArray = new Uint8Array([230, 176, 180, 240, 159, 152, 130]); let escapedUtf8SeqArray = new Uint8Array([195, 166, 194, 176, 194, 180]); let invalidUtf8Seq = new Uint8Array([0, 1, 255]); assert(decode(validUtf8SeqArray) == "水😂"); assert(decode(escapedUtf8SeqArray) == "\xe6\xb0\xb4"); // same behavior as nodejs assert(decode(invalidUtf8Seq) == "\x00\x01\ufffd"); assert(areEqual(encode(decode(validUtf8SeqArray)), validUtf8SeqArray)); assert(areEqual(encode(decode(escapedUtf8SeqArray)), escapedUtf8SeqArray)); // same behavior as nodejs assert( areEqual( encode(decode(invalidUtf8Seq)), new Uint8Array([0, 1, 239, 191, 189]) ) ); } export function uint8array_to_latin1_string_tests() { let happensToBeUtf8Seq = new Uint8Array([230, 176, 180]); let validLatin1InvalidUtf8 = new Uint8Array([0, 1, 255]); let ascii = new Uint8Array([65, 66, 67]); assert(str(happensToBeUtf8Seq) == "\xe6\xb0\xb4"); assert(str(validLatin1InvalidUtf8) == "\x00\x01\xff"); assert(str(ascii) == "\x41\x42\x43"); assert(areEqual(bytes(str(happensToBeUtf8Seq)), happensToBeUtf8Seq)); assert(areEqual(bytes(str(validLatin1InvalidUtf8)), validLatin1InvalidUtf8)); assert(areEqual(bytes(str(ascii)), ascii)); } ================================================ FILE: tests/src/constructor-validation/1-parameter-not-set-in-constructor.ts ================================================ import { NearBindgen, LookupMap } from "near-sdk-js"; @NearBindgen({}) export class ConstructorValidation { map: LookupMap; name: string; constructor() { this.map = new LookupMap("a"); } } ================================================ FILE: tests/src/constructor-validation/all-parameters-set-in-constructor.ts ================================================ import { NearBindgen, LookupMap, call } from "near-sdk-js"; @NearBindgen({}) export class ConstructorValidation { map: LookupMap; name: string; constructor() { this.map = new LookupMap("a"); this.name = ""; } @call({}) get() { return { status: "ok" }; } } ================================================ FILE: tests/src/constructor-validation/no-constructor.ts ================================================ import { NearBindgen, LookupMap } from "near-sdk-js"; @NearBindgen({}) export class ConstructorValidation { map: LookupMap; name: string; } ================================================ FILE: tests/src/constructor-validation/no-parameters-set-in-constructor.ts ================================================ import { NearBindgen, LookupMap } from "near-sdk-js"; @NearBindgen({}) export class ConstructorValidation { map: LookupMap; name: string; constructor() { // } } ================================================ FILE: tests/src/context_api.js ================================================ import { near, bytes } from "near-sdk-js"; // Functions consumed by the context api tests export function get_current_account_id() { near.valueReturn(near.currentAccountId()); } export function get_signer_account_id() { near.valueReturn(near.signerAccountId()); } export function get_predecessor_account_id() { near.valueReturn(near.predecessorAccountId()); } export function get_signer_account_pk() { near.valueReturnRaw(near.signerAccountPk()); } export function get_input() { near.valueReturnRaw(near.inputRaw()); } export function get_storage_usage() { near.valueReturn(near.storageUsage().toString()); } export function get_block_height() { near.valueReturn(near.blockHeight().toString()); } export function get_block_timestamp() { near.valueReturn(near.blockTimestamp().toString()); } export function get_epoch_height() { near.valueReturn(near.epochHeight().toString()); } export function get_attached_deposit() { near.valueReturn(JSON.stringify(near.attachedDeposit().toString())); } export function get_prepaid_gas() { near.valueReturn(near.prepaidGas().toString()); } export function get_used_gas() { near.valueReturn(near.usedGas().toString()); } export function get_random_seed() { near.valueReturnRaw(near.randomSeed()); } export function get_validator_stake() { near.valueReturn(near.validatorStake(near.signerAccountId()).toString()); } export function get_total_stake() { near.valueReturn(near.validatorTotalStake().toString()); } ================================================ FILE: tests/src/date-serialization.ts ================================================ import { near, NearBindgen, call, view } from "near-sdk-js"; /** * Simple class used for testing of the `dateField`. * - Includes methods: * - `getDateField()` - returns the current `dateField` value. * - `setDateField(args: { dateField })` - used to change the current `dateField` value. * - `getDateFieldAsMilliseconds()` - returns the `dateField` value in milliseconds. * @param dateField - Simple `Date` used for testing. */ @NearBindgen({}) export class DateSerializationTest { dateField: Date; constructor() { this.dateField = new Date(0); } @view({}) getDateField(): Date { near.log(`getDateField: ${this.dateField}`); return this.dateField; } @call({}) setDateField(args: { dateField: Date }): void { const dateField = new Date(args.dateField); near.log(`setDateField: ${dateField}`); this.dateField = dateField; } @view({}) getDateFieldAsMilliseconds(): number { near.log(`getDateFieldAsMilliseconds: ${this.dateField.getTime()}`); return this.dateField.getTime(); } } ================================================ FILE: tests/src/decorators/payable.ts ================================================ import { near, NearBindgen, call, view } from "near-sdk-js"; /** * Simple class used for testing of the `@call` decorator method with `option.payableFunction`, * which identifies whether the function can accept an attached deposit. * - `option.payableFunction` set to `true` * - `option.payableFunction` set to `false` * - Includes methods: * - `setValueWithPayableFunction({ value })` - used to change the current value and can accept an attached deposit. * - `setValueWithNotPayableFunction({ value })` - used to change the current value and cannot accept an attached deposit. * - `setValueWithNotPayableFunctionByDefault({ value })` - used to change the current value and cannot accept an attached deposit, * default behavior. * - `getValue()` - returns the current value * @param value - Simple string used for testing. */ @NearBindgen({}) export class PayableTest { value: string; constructor() { this.value = ""; } @call({ payableFunction: true }) setValueWithPayableFunction({ value }: { value: string }): void { near.log(`payableFunction: ${value}`); this.value = value; } @call({ payableFunction: false }) setValueWithNotPayableFunction({ value }: { value: string }): void { near.log(`notPayableFunction: ${value}`); this.value = value; } @call({}) setValueWithNotPayableFunctionByDefault({ value }: { value: string }): void { near.log(`notPayableFunctionDefault: ${value}`); this.value = value; } @view({}) getValue(): string { return this.value; } } ================================================ FILE: tests/src/decorators/private.ts ================================================ import { near, NearBindgen, call, view } from "near-sdk-js"; /** * Simple class used for testing of the `@call` decorator method with `option.privateFunction`, * which identifies whether the function can be called by other contracts. * - `option.privateFunction` set to `true` * - `option.privateFunction` set to `false` * - Includes methods: * - `setValueWithPrivateFunction({ value })` - used to change the current value and cannot be called by other contracts. * - `setValueWithNotPrivateFunction({ value })` - used to change the current value and can be called by other contracts. * - `setValueWithNotPrivateFunctionByDefault({ value })` - used to change the current value and can be called by other contracts, * default behavior. * - `getValue()` - returns the current value * @param value - Simple string used for testing. */ @NearBindgen({}) export class PrivateTest { value: string; constructor() { this.value = ""; } @call({ privateFunction: true }) setValueWithPrivateFunction({ value }: { value: string }): void { near.log(`setValueWithPrivateFunction: ${value}`); this.value = value; } @call({ privateFunction: false }) setValueWithNotPrivateFunction({ value }: { value: string }): void { near.log(`setValueWithNotPrivateFunction: ${value}`); this.value = value; } @call({}) setValueWithNotPrivateFunctionByDefault({ value }: { value: string }): void { near.log(`setValueWithNotPrivateFunctionByDefault: ${value}`); this.value = value; } @view({}) getValue(): string { return this.value; } } ================================================ FILE: tests/src/decorators/require_init_false.ts ================================================ import { near, NearBindgen, call, view, initialize } from "near-sdk-js"; /** * Simple class used for testing of the `NearBindgen` decorator with `option.requireInit`, * which identifies whether the contract requires initialization or not. * - `option.requireInit` set to `false` - Contract does not require initialization. * - Includes methods: * - `init()` - used for initializing the class * - `getStatus()` - used to get the current status param * - `setStatus()` - used to change the current status * @param status - Simple string used for testing. */ @NearBindgen({ requireInit: false }) export class NBTest { status: string; constructor() { this.status = ""; } @initialize({}) init({ status }: { status: string }): void { near.log(`init: ${status}`); this.status = status; } @view({}) getStatus(): string { near.log(`getStatus: ${this.status}`); return this.status; } @call({}) setStatus({ status }: { status: string }): void { near.log(`setStatus: ${status}`); this.status = status; } } ================================================ FILE: tests/src/decorators/require_init_true.ts ================================================ import { near, NearBindgen, call, view, initialize } from "near-sdk-js"; /** * Simple class used for testing of the `NearBindgen` decorator with `option.requireInit`, * which identifies whether the contract requires initialization or not. * - `option.requireInit` set to `true` - Contract requires initialization. * - Includes methods: * - `init()` - used for initializing the class * - `getStatus()` - used to get the current status param * - `setStatus()` - used to change the current status * @param status - Simple string used for testing. */ @NearBindgen({ requireInit: true }) export class NBTest { status: string; constructor() { this.status = ""; } @initialize({}) init({ status }: { status: string }): void { near.log(`init: ${status}`); this.status = status; } @view({}) getStatus(): string { near.log(`getStatus: ${this.status}`); return this.status; } @call({}) setStatus({ status }: { status: string }): void { near.log(`setStatus: ${status}`); this.status = status; } } ================================================ FILE: tests/src/function-params.js ================================================ import { NearBindgen, call, view, near } from "near-sdk-js"; /** * Simple contract used to test function params. * - Includes methods: * - `set_values({ param1, param2, param3 })` - change all the values. * - `get_values()` - returns an object containing all the values. * @param val1 - Simple string used for testing. * @param val2 - Simple string used for testing. * @param val3 - Simple string used for testing. */ @NearBindgen({}) export class FunctionParamsTestContract { constructor() { this.val1 = "default1"; this.val2 = "default2"; this.val3 = "default3"; } @call({}) set_values({ param1, param2, param3 }) { near.log(JSON.stringify({ param1, param2, param3 })); this.val1 = param1; this.val2 = param2; this.val3 = param3; } @view({}) get_values() { return { val3: this.val3, val2: this.val2, val1: this.val1 }; } } ================================================ FILE: tests/src/highlevel-promise.js ================================================ import { NearBindgen, call, NearPromise, near } from "near-sdk-js"; import { PublicKey } from "near-sdk-js"; function callingData() { return { currentAccountId: near.currentAccountId(), signerAccountId: near.signerAccountId(), predecessorAccountId: near.predecessorAccountId(), input: near.input(), }; } function arrayN(n) { return [...Array(Number(n)).keys()]; } /** * More information about this class, can be found in - benchmark/README.md/Highlevel collection */ @NearBindgen({}) export class HighlevelPromiseContract { @call({}) test_promise_batch_stake() { let promise = NearPromise.new("highlevel-promise.test.near").stake( 100000000000000000000000000000n, new PublicKey(near.signerAccountPk()) ); return promise; } @call({}) test_promise_batch_create_transfer() { let promise = NearPromise.new("a.highlevel-promise.test.near") .createAccount() .transfer(10000000000000000000000000n); return promise; } @call({}) test_promise_add_full_access_key() { let promise = NearPromise.new("c.highlevel-promise.test.near") .createAccount() .transfer(10000000000000000000000000n) .addFullAccessKey(new PublicKey(near.signerAccountPk())); return promise; } @call({}) test_promise_add_function_call_access_key() { let promise = NearPromise.new("d.highlevel-promise.test.near") .createAccount() .transfer(10000000000000000000000000n) .addAccessKey( new PublicKey(near.signerAccountPk()), 250000000000000000000000n, "highlevel-promise.test.near", "test_promise_batch_create_transfer" ); return promise; } @call({}) test_delete_account() { let promise = NearPromise.new("e.highlevel-promise.test.near") .createAccount() .transfer(10000000000000000000000000n) .deleteAccount(near.signerAccountId()); return promise; } @call({}) test_promise_then() { let promise = NearPromise.new("callee-contract.test.near") .functionCall("cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13)) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback", JSON.stringify({ callbackArg1: "def" }), 0, 2 * Math.pow(10, 13) ) ); return promise; } @call({}) test_promise_and() { let promise = NearPromise.new("callee-contract.test.near").functionCall( "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); let promise2 = NearPromise.new("callee-contract.test.near").functionCall( "cross_contract_callee", "def", 0, 2 * Math.pow(10, 13) ); let retPromise = promise .and(promise2) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback", JSON.stringify({ callbackArg1: "ghi" }), 0, 3 * Math.pow(10, 13) ) ); return retPromise; } @call({}) cross_contract_callback({ callbackArg1 }) { near.log("in callback"); return { ...callingData(), promiseResults: arrayN(near.promiseResultsCount()).map((i) => near.promiseResult(i) ), callbackArg1, }; } @call({}) cross_contract_callback_write_state() { // Attempt to write something in state. If this one is successfully executed and not revoked, these should be in state near.storageWrite("aaa", "bbb"); near.storageWrite("ccc", "ddd"); near.storageWrite("eee", "fff"); } @call({}) callee_panic() { let promise = NearPromise.new("callee-contract.test.near").functionCall( "just_panic", "", 0, 2 * Math.pow(10, 13) ); return promise; } @call({}) before_and_after_callee_panic() { near.log("log before call the callee"); let promise = NearPromise.new("callee-contract.test.near").functionCall( "just_panic", "", 0, 2 * Math.pow(10, 13) ); near.log("log after call the callee"); return promise; } @call({}) callee_panic_then() { let promise = NearPromise.new("callee-contract.test.near") .functionCall("just_panic", "", 0, 2 * Math.pow(10, 13)) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback_write_state", "", 0, 2 * Math.pow(10, 13) ) ); return promise; } @call({}) callee_panic_and() { let promise = NearPromise.new("callee-contract.test.near").functionCall( "just_panic", "", 0, 2 * Math.pow(10, 13) ); let promise2 = NearPromise.new("callee-contract.test.near").functionCall( "write_some_state", "", 0, 2 * Math.pow(10, 13) ); let retPromise = promise .and(promise2) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback_write_state", "", 0, 3 * Math.pow(10, 13) ) ); return retPromise; } @call({}) callee_success_then_panic() { let promise = NearPromise.new("callee-contract.test.near") .functionCall("write_some_state", "abc", 0, 2 * Math.pow(10, 13)) .then( NearPromise.new("callee-contract.test.near").functionCall( "just_panic", "", 0, 2 * Math.pow(10, 13) ) ); near.storageWrite("aaa", "bbb"); return promise; } @call({}) handler({ promiseId }) { // example to catch and handle one given promiseId. This is to simulate when you know some // promiseId can be possibly fail and some promiseId can never fail. If more than one promiseId // can be failed. a similar approach can be applied to all promiseIds. let res; try { res = near.promiseResult(promiseId); } catch (e) { throw new Error("caught error in the callback: " + e.toString()); } return "callback got " + res; } @call({}) handle_error_in_promise_then() { let promise = NearPromise.new("callee-contract.test.near") .functionCall("just_panic", "", 0, 2 * Math.pow(10, 13)) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "handler", JSON.stringify({ promiseId: 0 }), 0, 2 * Math.pow(10, 13) ) ); return promise; } @call({}) handle_error_in_promise_then_after_promise_and() { let promise = NearPromise.new("callee-contract.test.near") .functionCall("cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13)) .and( NearPromise.new("callee-contract.test.near").functionCall( "just_panic", "", 0, 2 * Math.pow(10, 13) ) ) .then( NearPromise.new("highlevel-promise.test.near").functionCall( "handler", JSON.stringify({ promiseId: 1 }), 0, 2 * Math.pow(10, 13) ) ); return promise; } @call({}) not_return_not_build() { // let promise = NearPromise.new("b.highlevel-promise.test.near") // .createAccount() // .transfer(10000000000000000000000000n); // nothing happens } @call({}) build_not_return() { let promise = NearPromise.new("b.highlevel-promise.test.near") .createAccount() .transfer(10000000000000000000000000n); promise.build(); // doesn't return the promise, but promise should be created and executed } } ================================================ FILE: tests/src/log_panic_api.js ================================================ import { near, bytes } from "near-sdk-js"; // Functions consumed by the log panic tests export function log_expected_input_tests() { // log ascii string near.log("abc"); // log string with utf-8 chars near.log("水"); // log number near.log(333); // log aribrary byte sequence near.log(bytes("\x00\x01\xff")); // log valid utf8 seqence near.log(bytes("\xe6\xb0\xb4")); // log valid utf8 sequence near.logUtf8(bytes("\xe6\xb0\xb4")); // log valid utf16 sequence near.logUtf16(bytes("\x34\x6c")); } export function log_invalid_utf8_sequence_test() { near.logUtf8(bytes("\x00\x01\xff")); } export function log_invalid_utf16_sequence_test() { near.logUtf16(bytes("\x00\x01\xff")); } export function panic_test() { throw Error(); } export function panic_ascii_test() { throw Error("abc"); } export function panic_js_number() { throw Error(356); } export function panic_js_undefined() { throw Error(undefined); } export function panic_js_null() { throw Error(null); } export function panic_utf8_test() { throw Error("水"); } export function panicUtf8_valid_utf8_sequence() { near.panicUtf8(bytes("\xe6\xb0\xb4")); } export function panicUtf8_invalid_utf8_sequence() { near.panicUtf8(bytes("\x00\x01\xff")); } ================================================ FILE: tests/src/lookup-map.js ================================================ import { NearBindgen, call, view, LookupMap } from "near-sdk-js"; import { House, Room } from "./model.js"; /** * Simple contract used to test `lookupMap` functionality. * - Includes methods: * - `get({ key })` - Get the data stored at the provided key. * - `containsKey({ key })` - Checks whether the collection contains the value. * - `set({ key, value })` - Store a new value at the provided key. * - `remove_key({ key })` - Removes and retrieves the element with the provided key. * - `extend({ kvs })` - Extends the current collection with the passed in array of key-value pairs. * - `add_house()` - Adds a test `House` object in the `lookupMap`. * - `get_house()` - Returns a `string` containing the `house.describe()` + `room.describe()` results. * @param lookupMap - Simple `LookupMap` used for testing. */ @NearBindgen({}) export class LookupMapTestContract { constructor() { this.lookupMap = new LookupMap("a"); } @view({}) get({ key }) { return this.lookupMap.get(key); } @view({}) containsKey({ key }) { return this.lookupMap.containsKey(key); } @call({}) set({ key, value }) { this.lookupMap.set(key, value); } @call({}) remove_key({ key }) { this.lookupMap.remove(key); } @call({}) extend({ kvs }) { this.lookupMap.extend(kvs); } @call({}) add_house() { this.lookupMap.set( "house1", new House("house1", [ new Room("room1", "200sqft"), new Room("room2", "300sqft"), ]) ); } @view({}) get_house() { const houseObject = this.lookupMap.get("house1"); // restore class object from serialized data const house = new House(houseObject.name, houseObject.rooms); const roomObject = house.rooms[0]; const room = new Room(roomObject.name, roomObject.size); return house.describe() + room.describe(); } } ================================================ FILE: tests/src/lookup-set.js ================================================ import { NearBindgen, call, view, LookupSet } from "near-sdk-js"; import { House, Room } from "./model.js"; /** * Simple contract used to test `lookupSet` functionality. * - Includes methods: * - `contains({ key })` - Checks whether the value is present in the set. * - `set({ key })` - f the set did not have this value present, `true` is returned. If the set did have this value present, `false` is returned. * - `remove_key({ key })` - Removes and retrieves the element with the provided key. * - `extend({ keys })` - Extends the current collection with the passed array of elements. * - `add_house({ name, rooms })` - Adds a test `House` object in the `lookupSet`. * - `house_exist({ name, rooms })` - Checks whether the value is present in the set, `true` if present, `false` if not. * @param lookupSet - Simple `LookupSet` used for testing. */ @NearBindgen({}) export class LookupSetTestContract { constructor() { this.lookupSet = new LookupSet("a"); } @view({}) contains({ key }) { return this.lookupSet.contains(key); } @call({}) set({ key }) { this.lookupSet.set(key); } @call({}) remove_key({ key }) { this.lookupSet.remove(key); } @call({}) extend({ keys }) { this.lookupSet.extend(keys); } @call({}) add_house({ name, rooms }) { let house = new House(name, []); for (let r of rooms) { house.rooms.push(new Room(r.name, r.size)); } this.lookupSet.set(house); } @view({}) house_exist({ name, rooms }) { let house = new House(name, []); for (let r of rooms) { house.rooms.push(new Room(r.name, r.size)); } return this.lookupSet.contains(house); } } ================================================ FILE: tests/src/math_api.js ================================================ import { near, bytes } from "near-sdk-js"; // Functions consumed by the math api tests export function test_sha256() { near.valueReturnRaw(near.sha256(bytes("tesdsst"))); } export function test_keccak256() { near.valueReturnRaw(near.keccak256(bytes("tesdsst"))); } export function test_keccak512() { near.valueReturnRaw(near.keccak512(bytes("tesdsst"))); } export function test_ripemd160() { near.valueReturnRaw(near.ripemd160(bytes("tesdsst"))); } export function test_ecrecover() { let hash = new Uint8Array([ 206, 6, 119, 187, 48, 186, 168, 207, 6, 124, 136, 219, 152, 17, 244, 51, 61, 19, 27, 248, 188, 241, 47, 231, 6, 93, 33, 29, 206, 151, 16, 8, ]); let sign = new Uint8Array([ 144, 242, 123, 139, 72, 141, 176, 11, 0, 96, 103, 150, 210, 152, 127, 106, 95, 89, 174, 98, 234, 5, 239, 254, 132, 254, 245, 184, 176, 229, 73, 152, 74, 105, 17, 57, 173, 87, 163, 240, 185, 6, 99, 118, 115, 170, 47, 99, 209, 245, 92, 177, 166, 145, 153, 212, 0, 158, 234, 35, 206, 173, 220, 147, ]); let v = 1; let malleabilityFlag = 1; let ret = near.ecrecover(hash, sign, v, malleabilityFlag); near.valueReturnRaw(ret); } ================================================ FILE: tests/src/middlewares.ts ================================================ import { NearBindgen, near, call, view, initialize, middleware, } from "near-sdk-js"; /** * Simple contract used for testing the `@middleware` decorator. * The method that gets called with the same arguments that are passed to the function it is wrapping. * @param args - Arguments that will be passed to the function - immutable. */ @NearBindgen({ requireInit: true }) export class Contract { @initialize({}) @middleware((...args) => near.log(`Log from middleware: ${args}`)) // eslint-disable-next-line @typescript-eslint/no-empty-function init({ randomData: _ }: { randomData: string }) {} @call({}) @middleware((...args) => near.log(`Log from middleware: ${args}`)) // eslint-disable-next-line @typescript-eslint/no-empty-function add({ id: _, text: _t }: { id: string; text: string }) {} @view({}) @middleware((...args) => near.log(`Log from middleware: ${args}`)) get({ id, accountId }: { id: string; accountId: string }): { id: string; accountId: string; } { return { id: accountId, accountId: id }; } @view({}) @middleware( (...args) => near.log(`Log from middleware: ${args}`), () => near.log("Second log!") ) get_two({ id, accountId }: { id: string; accountId: string }): { id: string; accountId: string; } { return { id: accountId, accountId: id }; } @view({}) get_private(): { id: string; accountId: string } { return this.getFromPrivate({ id: "test", accountId: "tset" }); } @middleware((args) => near.log(`Log from middleware: ${args}`)) getFromPrivate({ id, accountId }: { id: string; accountId: string }): { id: string; accountId: string; } { return { id, accountId }; } } ================================================ FILE: tests/src/migrate.ts ================================================ import { NearBindgen, near, call, view, migrate } from "near-sdk-js"; /** * Simple class used for testing. * - `option.requireInit` set to `true` - Contract requires initialization. * - Includes methods: * - `increase({ n })` - increases the count with the given `n` * - `getCount()` - get the current count * - `migrFuncValueTo18()` - set the current count to `18` * @param count - Simple number used for testing. */ @NearBindgen({}) export class Counter { count = 0; @call({}) increase({ n = 1 }: { n: number }) { this.count += n; near.log(`Counter increased to ${this.count}`); } @view({}) getCount(): number { return this.count; } @migrate({}) migrFuncValueTo18(): void { near.log("Count: " + this.count); // expected to be 0 this.count = 18; } } ================================================ FILE: tests/src/model.js ================================================ /** * Simple model used for testing. * - Includes methods: * - `describe()` - returns a string `house ${this.name} has ${this.rooms.length} rooms.` * @param name - Simple `string` used for testing. * @param rooms - Simple `Room[]` used for testing. */ export class House { constructor(name, rooms) { this.name = name; this.rooms = rooms; } describe() { return `house ${this.name} has ${this.rooms.length} rooms. `; } } /** * Simple model used for testing. * - Includes methods: * - `describe()` - returns a string `room ${this.name} is ${this.size}.` * @param name - Simple `string` used for testing. * @param size - Simple `number` used for testing. */ export class Room { constructor(name, size) { this.name = name; this.size = size; } describe() { return `room ${this.name} is ${this.size}.`; } } ================================================ FILE: tests/src/promise_api.js ================================================ import { near } from "near-sdk-js"; // Functions consumed by the promise api tests function arrayN(n) { return [...Array(Number(n)).keys()]; } export function just_panic() { throw new Error("it just panic"); } export function write_some_state() { // Attempt to write something in state. If this one is successfully executed and not revoked, these should be in state near.storageWrite("aaa", "bbb"); near.storageWrite("ccc", "ddd"); near.storageWrite("eee", "fff"); } function callingData() { return { currentAccountId: near.currentAccountId(), signerAccountId: near.signerAccountId(), predecessorAccountId: near.predecessorAccountId(), input: near.input(), }; } export function cross_contract_callee() { near.valueReturn(JSON.stringify(callingData())); } export function cross_contract_call_gas() { near.valueReturn(near.prepaidGas().toString()); } export function cross_contract_callback() { near.valueReturn( JSON.stringify({ ...callingData(), promiseResults: arrayN(near.promiseResultsCount()).map((i) => near.promiseResult(i) ), }) ); } export function test_promise_create() { near.promiseCreate( "callee-contract.test.near", "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); } export function test_promise_create_gas_overflow() { near.promiseCreate( "callee-contract.test.near", "cross_contract_callee", "abc", 0, BigInt(2) ** BigInt(64) ); } export function test_promise_then() { let promiseId = near.promiseCreate( "callee-contract.test.near", "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); near.promiseThen( promiseId, "caller-contract.test.near", "cross_contract_callback", "def", 0, 2 * Math.pow(10, 13) ); } export function test_promise_and() { let promiseId = near.promiseCreate( "callee-contract.test.near", "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); let promiseId2 = near.promiseCreate( "callee-contract.test.near", "cross_contract_callee", "def", 0, 2 * Math.pow(10, 13) ); let promiseIdAnd = near.promiseAnd(promiseId, promiseId2); near.promiseThen( promiseIdAnd, "caller-contract.test.near", "cross_contract_callback", "ghi", 0, 3 * Math.pow(10, 13) ); } ================================================ FILE: tests/src/promise_batch_api.js ================================================ import { near, includeBytes } from "near-sdk-js"; // Functions consumed by the promise batch api tests export function test_promise_batch_stake() { let promiseId = near.promiseBatchCreate("caller2.test.near"); near.promiseBatchActionStake( promiseId, 100000000000000000000000000000n, near.signerAccountPk() ); near.promiseReturn(promiseId); } export function test_transfer_overflow() { let promiseId = near.promiseBatchCreate("c.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, BigInt(2) ** BigInt(128)); near.promiseReturn(promiseId); } export function test_promise_add_full_access_key() { let promiseId = near.promiseBatchCreate("c.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseBatchActionAddKeyWithFullAccess( promiseId, near.signerAccountPk(), 1n ); near.promiseReturn(promiseId); } export function test_delete_account() { let promiseId = near.promiseBatchCreate("e.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseBatchActionDeleteAccount(promiseId, near.signerAccountId()); near.promiseReturn(promiseId); } export function test_promise_add_function_call_access_key() { let promiseId = near.promiseBatchCreate("d.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseBatchActionAddKeyWithFunctionCall( promiseId, near.signerAccountPk(), 1n, 250000000000000000000000n, "caller2.test.near", "test_promise_batch_create_transfer" ); near.promiseReturn(promiseId); } export function test_promise_batch_create_transfer() { let promiseId = near.promiseBatchCreate("a.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseReturn(promiseId); } export function test_promise_batch_call_weight() { let promiseId = near.promiseBatchCreate("callee-contract.test.near"); near.promiseBatchActionFunctionCallWeight( promiseId, "cross_contract_call_gas", "abc", 0, 0, 1 ); near.promiseReturn(promiseId); } export function test_promise_batch_deploy_call() { let promiseId = near.promiseBatchCreate("b.caller2.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); // deploy content of promise_api.wasm to `b.caller2.test.near` near.promiseBatchActionDeployContract( promiseId, includeBytes("../build/promise_api.wasm") ); near.promiseBatchActionFunctionCall( promiseId, "cross_contract_callee", "abc", 0, 2 * Math.pow(10, 13) ); near.promiseReturn(promiseId); } ================================================ FILE: tests/src/public-key.js ================================================ import { near } from "near-sdk-js"; import { CurveType, PublicKey } from "near-sdk-js"; import { assert } from "near-sdk-js"; // Functions consumed by the public key tests function runtime_validate_public_key(prefix, public_key) { let promiseId = near.promiseBatchCreate(prefix + ".pk.test.near"); near.promiseBatchActionCreateAccount(promiseId); near.promiseBatchActionTransfer(promiseId, 10000000000000000000000000n); near.promiseBatchActionAddKeyWithFullAccess(promiseId, public_key, 1n); near.promiseReturn(promiseId); } export function test_add_signer_key() { runtime_validate_public_key("aa", near.signerAccountPk()); } export function test_add_ed25519_key_bytes() { let pk = new PublicKey( new Uint8Array([ // CurveType.ED25519 = 0 0, // ED25519 PublicKey data 186, 44, 216, 49, 157, 48, 151, 47, 23, 244, 137, 69, 78, 150, 54, 42, 30, 248, 110, 26, 205, 18, 137, 154, 10, 208, 26, 183, 65, 166, 223, 18, ]) ); runtime_validate_public_key("a", pk.data); } export function test_add_ed25519_key_string() { let k = "ed25519:DXkVZkHd7WUUejCK7i74uAoZWy1w9AZqshhTHxhmqHuB"; let pk = PublicKey.fromString(k); runtime_validate_public_key("b", pk.data); } export function test_add_secp256k1_key_bytes() { let pk = new PublicKey( new Uint8Array([ // CurveType.SECP256K1 = 1 1, // SECP256K1 PublicKey data 242, 86, 198, 230, 200, 11, 33, 63, 42, 160, 176, 23, 68, 35, 93, 81, 92, 89, 68, 53, 190, 101, 27, 21, 136, 58, 16, 221, 71, 47, 166, 70, 206, 98, 234, 243, 103, 13, 197, 203, 145, 0, 160, 202, 42, 85, 178, 193, 71, 193, 233, 163, 140, 228, 40, 135, 142, 125, 70, 225, 251, 113, 74, 153, ]) ); runtime_validate_public_key("c", pk.data); } export function test_add_secp256k1_key_string() { let k = "secp256k1:5r22SrjrDvgY3wdQsnjgxkeAbU1VcM71FYvALEQWihjM3Xk4Be1CpETTqFccChQr4iJwDroSDVmgaWZv2AcXvYeL"; let pk = PublicKey.fromString(k); runtime_validate_public_key("d", pk.data); } export function add_invalid_public_key() { runtime_validate_public_key( "e", new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) ); } export function curve_type() { assert( new PublicKey(near.signerAccountPk()).curveType() == CurveType.ED25519 ); } export function create_invalid_curve_type() { new PublicKey(new Uint8Array([2, 1])); } export function create_invalid_length() { new PublicKey(new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); } export function create_from_invalid_base58() { PublicKey.fromString("ed25519:!@#$%^&*"); } ================================================ FILE: tests/src/storage_api.js ================================================ import { near, bytes } from "near-sdk-js"; // Functions consumed by the storage api tests export function test_storage_write() { near.valueReturnRaw( bytes( near .storageWriteRaw(bytes("\x00tesdsst\xff"), bytes("\x00\x01\xff")) .toString() ) ); } export function test_storage_read() { near.valueReturnRaw(near.storageReadRaw(bytes("\x00tesdsst\xff"))); } export function test_storage_remove() { near.valueReturnRaw( bytes(near.storageRemoveRaw(bytes("\x00tesdsst\xff")).toString()) ); } export function test_storage_has_key() { near.valueReturnRaw( bytes(near.storageHasKeyRaw(bytes("\x00tesdsst\xff")).toString()) ); } export function test_storage_get_evicted() { near.storageWriteRaw(bytes("\x00tesdsst\xff"), bytes("\x00\x01\xff")); near.storageWriteRaw(bytes("\x00tesdsst\xff"), bytes("\x03\x01\xee")); near.valueReturnRaw(near.storageGetEvictedRaw()); } ================================================ FILE: tests/src/typescript.ts ================================================ import { NearBindgen, view } from "near-sdk-js"; @NearBindgen({}) export class TypeScriptTestContract { @view({}) bigint() { // JSON.stringify cannot seriaize a BigInt, need manually toString return (1n + 2n).toString(); } } ================================================ FILE: tests/src/unordered-map.js ================================================ import { NearBindgen, call, view, UnorderedMap } from "near-sdk-js"; import { House, Room } from "./model.js"; /** * Simple contract used for testing the `unorderedMap`. * - Includes methods: * - `len()` - Returns the current number of elements present in the map. * - `isEmpty()` - Checks whether the collection is empty. * - `get({ key })` - Get the data stored at the provided key. * - `set({ key, value })` - Store a new value at the provided key. * - `remove_key({ key })` - Removes and retrieves the element with the provided key. * - `clear()` - Remove all of the elements stored within the collection. * - `toArray()` - Return a JavaScript array of the data stored within the collection. * - `extend({ kvs })` - Extends the current collection with the passed in array of key-value pairs. * - `add_house()` - Store a new `House` object in the `unorderedMap` instance. * - `get_house()` - Retrieves the current `House` object from the `unorderedMap` and returns a string `house.describe() + room.describe()` * - `keys({ start, limit })` - Converts the deserialized data from storage to a JavaScript instance of the collection. * @param unorderedMap - Simple `UnorderedMap` used for testing. */ @NearBindgen({}) export class UnorderedMapTestContract { constructor() { this.unorderedMap = new UnorderedMap("a"); } @view({}) len() { return this.unorderedMap.length; } @view({}) isEmpty() { return this.unorderedMap.isEmpty(); } @view({}) get({ key }) { return this.unorderedMap.get(key); } @call({}) set({ key, value }) { this.unorderedMap.set(key, value); } @call({}) remove_key({ key }) { this.unorderedMap.remove(key); } @call({}) clear() { this.unorderedMap.clear(); } @view({}) toArray() { return this.unorderedMap.toArray(); } @call({}) extend({ kvs }) { this.unorderedMap.extend(kvs); } @call({}) add_house() { this.unorderedMap.set( "house1", new House("house1", [ new Room("room1", "200sqft"), new Room("room2", "300sqft"), ]) ); } @view({}) get_house() { const house = this.unorderedMap.get("house1", { reconstructor: (rawHouse) => new House( rawHouse.name, rawHouse.rooms.map((rawRoom) => new Room(rawRoom.name, rawRoom.size)) ), }); const room = house.rooms[0]; return house.describe() + room.describe(); } @view({}) keys({ start, limit }) { return this.unorderedMap.keys({ start, limit }); } } ================================================ FILE: tests/src/unordered-set.js ================================================ import { NearBindgen, call, view, UnorderedSet } from "near-sdk-js"; import { House, Room } from "./model.js"; /** * Simple contract used for testing the `unorderedSet`. * - Includes methods: * - `len()` - Returns the current number of elements present in the set. * - `isEmpty()` - Checks whether the collection is empty. * - `contains({ element })` - Checks whether the collection contains the value. * - `set({ element }) ` - Store a new value at the provided key. * - `remove_key({ element })` - Removes and retrieves the element. * - `clear()` - Remove all of the elements stored within the collection. * - `toArray()` - Return a JavaScript array of the data stored within the collection. * - `elements({ start, limit })` - Converts the deserialized data from storage to a JavaScript instance of the collection. * - `extend({ elements })` - Extends the current collection with the passed in array of elements. * - `add_house({ name, rooms })` - Store a new `House` object in the `unorderedMap` instance. * - `house_exist({ name, rooms })` - Checks whether the collection contains the passed `House` object. * @param unorderedSet - Simple `UnorderedSet` used for testing. */ @NearBindgen({}) export class UnorderedSetTestContract { constructor() { this.unorderedSet = new UnorderedSet("a"); } @view({}) len() { return this.unorderedSet.length; } @view({}) isEmpty() { return this.unorderedSet.isEmpty(); } @view({}) contains({ element }) { return this.unorderedSet.contains(element); } @call({}) set({ element }) { this.unorderedSet.set(element); } @call({}) remove_key({ element }) { this.unorderedSet.remove(element); } @call({}) clear() { this.unorderedSet.clear(); } @view({}) toArray() { const res = this.unorderedSet.toArray(); return res; } @view({}) elements({ start, limit }) { return this.unorderedSet.elements({ start, limit }); } @call({}) extend({ elements }) { this.unorderedSet.extend(elements); } @call({}) add_house({ name, rooms }) { let house = new House(name, []); for (let r of rooms) { house.rooms.push(new Room(r.name, r.size)); } this.unorderedSet.set(house); } @view({}) house_exist({ name, rooms }) { let house = new House(name, []); for (let r of rooms) { house.rooms.push(new Room(r.name, r.size)); } return this.unorderedSet.contains(house); } } ================================================ FILE: tests/src/vector.js ================================================ import { NearBindgen, call, view, Vector } from "near-sdk-js"; import { House, Room } from "./model.js"; /** * Simple contract used for testing the `vector`. * - Includes methods: * - `len()` - Returns the current number of elements. * - `isEmpty()` - Checks whether the collection is empty. * - `get({ index })` - Get the value at the current index. * - `push({ value })` - Adds data to the collection. * - `pop()` - Removes and retrieves the element with the highest index.. * - `clear()` - Remove all of the elements stored within the collection. * - `toArray()` - Return a JavaScript array of the data stored within the collection. * - `extend({ kvs })` - Extends the current collection with the passed in array of elements. * - `replace({ index, value })` - Replaces the data stored at the provided index with the provided data and returns the previously stored data. * - `swapRemove({ index })` - Removes an element from the vector and returns it in serialized form. * The removed element is replaced by the last element of the vector. * Does not preserve ordering, but is `O(1)`. * - `add_house()` - Store a new `House` object in the `unorderedMap` instance. * - `get_house()` - Returns the `House` object at index `0`. * @param vector - Simple `Vector` used for testing. */ @NearBindgen({}) export class VectorTestContract { constructor() { this.vector = new Vector("a"); } @view({}) len() { return this.vector.length; } @view({}) isEmpty() { return this.vector.isEmpty(); } @view({}) get({ index }) { return this.vector.get(index); } @call({}) push({ value }) { this.vector.push(value); } @call({}) pop() { this.vector.pop(); } @call({}) clear() { this.vector.clear(); } @view({}) toArray() { return this.vector.toArray(); } @call({}) extend({ kvs }) { this.vector.extend(kvs); } @call({}) replace({ index, value }) { this.vector.replace(index, value); } @call({}) swapRemove({ index }) { this.vector.swapRemove(index); } @call({}) add_house() { this.vector.push( new House("house1", [ new Room("room1", "200sqft"), new Room("room2", "300sqft"), ]) ); } @view({}) get_house() { return this.vector.get(0); } } ================================================ FILE: tests/tsconfig.json ================================================ { "compilerOptions": { "moduleResolution": "node", "experimentalDecorators": true, "target": "es2020", "noEmit": true }, "exclude": ["node_modules"] } ================================================ FILE: turbo.json ================================================ { "$schema": "https://turborepo.org/schema.json", "pipeline": { "tests#build":{ }, "bench#build":{ "dependsOn": ["tests#build"] }, "examples#build":{ }, "near-sdk-js#build":{ }, "near-contract-standards#build":{ }, "test": { "inputs": [], "outputs": [] }, "lint": { "inputs": [], "outputs": [] }, "format": { "inputs": [], "outputs": [] } } } ================================================ FILE: typedoc.json ================================================ { "includeVersion": true, "entryPoints": [ "packages/near-sdk-js" ], "entryPointStrategy": "packages", "githubPages": true, "validation": { "notExported": false }, "out": "docs", "readme": "./README.md" }