├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── .mocharc.js ├── .prettierignore ├── .prettierrc.js ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODEOWNERS ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README-ja.md ├── README.md ├── architecture.png ├── codecov.yml ├── openapitools.json ├── package-lock.json ├── package.json ├── pay-id-api-spec └── pay-id.v1.yml ├── scripts ├── init_mac.sh ├── regenerate_pay_id_api.sh └── regenerate_protos.sh ├── src ├── Common │ └── utils.ts ├── ILP │ ├── auth │ │ ├── ilp-credentials.ts │ │ └── ilp-credentials.web.ts │ ├── default-ilp-client.ts │ ├── grpc-ilp-network-client.ts │ ├── grpc-ilp-network-client.web.ts │ ├── ilp-client-decorator.ts │ ├── ilp-client.ts │ ├── ilp-error.ts │ ├── ilp-network-client.ts │ ├── index.ts │ └── model │ │ ├── account-balance.ts │ │ ├── index.ts │ │ ├── payment-request.ts │ │ └── payment-result.ts ├── PayID │ ├── pay-id-client.ts │ ├── pay-id-error.ts │ ├── xrp-pay-id-client-interface.ts │ └── xrp-pay-id-client.ts ├── XRP │ ├── core-xrpl-client-interface.ts │ ├── core-xrpl-client.ts │ ├── default-xrp-client.ts │ ├── index.ts │ ├── issued-currency-client.ts │ ├── network-clients │ │ ├── grpc-network-client-interface.ts │ │ ├── grpc-xrp-network-client.ts │ │ ├── grpc-xrp-network-client.web.ts │ │ ├── web-socket-network-client-interface.ts │ │ └── web-socket-network-client.ts │ ├── protobuf-wrappers │ │ ├── index.ts │ │ ├── xrp-account-delete.ts │ │ ├── xrp-account-set.ts │ │ ├── xrp-check-cancel.ts │ │ ├── xrp-check-cash.ts │ │ ├── xrp-check-create.ts │ │ ├── xrp-currency-amount.ts │ │ ├── xrp-currency.ts │ │ ├── xrp-deposit-preauth.ts │ │ ├── xrp-escrow-cancel.ts │ │ ├── xrp-escrow-create.ts │ │ ├── xrp-escrow-finish.ts │ │ ├── xrp-memo.ts │ │ ├── xrp-offer-cancel.ts │ │ ├── xrp-offer-create.ts │ │ ├── xrp-path-element.ts │ │ ├── xrp-path.ts │ │ ├── xrp-payment-channel-claim.ts │ │ ├── xrp-payment-channel-create.ts │ │ ├── xrp-payment-channel-fund.ts │ │ ├── xrp-payment.ts │ │ ├── xrp-set-regular-key.ts │ │ ├── xrp-signer-entry.ts │ │ ├── xrp-signer-list-set.ts │ │ ├── xrp-signer.ts │ │ ├── xrp-transaction.ts │ │ ├── xrp-trust-set.ts │ │ └── xrpl-issued-currency.ts │ ├── reliable-submission-xrp-client.ts │ ├── shared │ │ ├── account-root-flags.ts │ │ ├── account-set-flag.ts │ │ ├── gateway-balances.ts │ │ ├── index.ts │ │ ├── issued-currency.ts │ │ ├── offer-create-flag.ts │ │ ├── payment-flags.ts │ │ ├── raw-transaction-status.ts │ │ ├── rippled-error-messages.ts │ │ ├── rippled-web-socket-schema.ts │ │ ├── send-xrp-details.ts │ │ ├── transaction-prefix.ts │ │ ├── transaction-result.ts │ │ ├── transaction-status.ts │ │ ├── trust-set-flag.ts │ │ ├── trustline.ts │ │ ├── xrp-error.ts │ │ ├── xrp-transaction-type.ts │ │ └── xrp-utils.ts │ ├── xrp-client-decorator.ts │ ├── xrp-client-interface.ts │ └── xrp-client.ts ├── Xpring │ ├── xpring-client.ts │ └── xpring-error.ts └── index.ts ├── test ├── Common │ ├── Fakes │ │ └── fake-grpc-error.ts │ ├── Helpers │ │ └── result.ts │ └── utils.test.ts ├── ILP │ ├── default-ilp-client.test.ts │ ├── fakes │ │ ├── fake-default-ilp-client.ts │ │ └── fake-ilp-network-client.ts │ ├── ilp-credentials-web.test.ts │ ├── ilp-credentials.test.ts │ └── ilp-integration.test.ts ├── PayID │ ├── fakes │ │ └── fake-xrp-pay-id-client.ts │ ├── pay-id-client.test.ts │ ├── pay-id-integration.test.ts │ ├── pay-id-utils.test.ts │ └── xrp-pay-id-client.test.ts ├── XRP │ ├── core-xrpl-client.test.ts │ ├── default-xrp-client.test.ts │ ├── fakes │ │ ├── fake-core-xrpl-client.ts │ │ ├── fake-web-socket-network-client.ts │ │ ├── fake-xrp-client.ts │ │ ├── fake-xrp-network-client.ts │ │ ├── fake-xrp-protobufs.ts │ │ └── fake-xrp-transaction-type-protobufs.ts │ ├── helpers │ │ └── xrp-test-utils.ts │ ├── issued-currency-client-integration.test.ts │ ├── issued-currency-client.test.ts │ ├── issued-currency-payment-integration.test.ts │ ├── protos-models-flags-utils │ │ ├── gateway-balances.test.ts │ │ ├── raw-transaction-status.test.ts │ │ ├── rippled-flags.test.ts │ │ ├── trust-line.test.ts │ │ ├── xrp-protocol-buffer-conversion.test.ts │ │ ├── xrp-transaction-type-proto-conversion.test.ts │ │ └── xrp-utils.test.ts │ ├── reliable-submission-xrp-client.test.ts │ ├── web-socket-network-client.test.ts │ └── xrp-client-integration.test.ts └── Xpring │ ├── xpring-client-integration.test.ts │ └── xpring-client.test.ts ├── tsconfig.eslint.json ├── tsconfig.json └── webpack.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Copyright Ripple Labs, 2019 2 | 3 | version: 2.1 4 | orbs: 5 | protobuf: izumin5210/protobuf@0.1.0 6 | codecov: codecov/codecov@1.0.4 7 | jobs: 8 | build: 9 | docker: 10 | - image: circleci/node:12.18.0 11 | 12 | working_directory: ~/repo 13 | 14 | steps: 15 | - checkout 16 | 17 | - restore_cache: 18 | keys: 19 | - v1-dependencies-{{ checksum "package.json" }} 20 | - v1-dependencies- 21 | 22 | - protobuf/install 23 | 24 | # Taken from: https://linuxize.com/post/install-java-on-debian-10/ 25 | - run: 26 | name: 'Install Java' 27 | command: | 28 | sudo apt update 29 | wget https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/pool/main/a/adoptopenjdk-14-hotspot/adoptopenjdk-14-hotspot_14.0.0+36-2_amd64.deb 30 | sudo apt install ./adoptopenjdk-14-hotspot_14.0.0+36-2_amd64.deb 31 | 32 | - run: 33 | name: 'Install protoc plugin' 34 | command: | 35 | curl -L https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64 --output protoc-gen-grpc-web 36 | sudo mv protoc-gen-grpc-web /usr/local/bin/ 37 | chmod +x /usr/local/bin/protoc-gen-grpc-web 38 | 39 | - run: 40 | name: 'Pull submodules' 41 | command: git submodule update --init --recursive 42 | 43 | - run: 44 | name: 'Install dependencies' 45 | command: | 46 | sudo npm -g i nyc codecov 47 | npm i 48 | npm rebuild 49 | 50 | - save_cache: 51 | paths: 52 | - node_modules 53 | key: v1-dependencies-{{ checksum "package.json" }} 54 | 55 | - run: 56 | name: 'Compile Typescript' 57 | command: | 58 | npm run build 59 | 60 | - run: 61 | name: 'Webpack' 62 | command: npm run webpack 63 | 64 | - run: 65 | name: 'Run Tests' 66 | no_output_timeout: 60m 67 | command: | 68 | nyc npm test 69 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Do not lint generated files. 2 | src/ILP/Generated 3 | src/PayID/Generated/ 4 | src/XRP/Generated 5 | build 6 | 7 | # Do not lint submodules 8 | hermes-ilp 9 | xpring-common-protocol-buffers 10 | rippled 11 | 12 | # Don't lint node_modules 13 | node_modules 14 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 4 | parser: '@typescript-eslint/parser', // Make ESLint compatible with TypeScript 5 | parserOptions: { 6 | // Enable linting rules with type information from our tsconfig 7 | tsconfigRootDir: __dirname, 8 | project: ['./tsconfig.eslint.json'], 9 | 10 | sourceType: 'module', // Allow the use of imports / ES modules 11 | 12 | ecmaFeatures: { 13 | impliedStrict: true, // Enable global strict mode 14 | }, 15 | }, 16 | 17 | // Specify global variables that are predefined 18 | env: { 19 | browser: true, // Enable browser global variables 20 | node: true, // Enable node global variables & Node.js scoping 21 | es2020: true, // Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020 22 | mocha: true, // Add Mocha testing global variables 23 | }, 24 | 25 | plugins: [ 26 | '@typescript-eslint', // Add some TypeScript specific rules, and disable rules covered by the typechecker 27 | 'import', // Add rules that help validate proper imports 28 | 'mocha', // Add rules for writing better Mocha tests 29 | 'prettier', // Allows running prettier as an ESLint rule, and reporting differences as individual linting issues 30 | ], 31 | 32 | extends: [ 33 | // ESLint recommended rules 34 | 'eslint:recommended', 35 | 36 | // Add TypeScript-specific rules, and disable rules covered by typechecker 37 | 'plugin:@typescript-eslint/eslint-recommended', 38 | 'plugin:@typescript-eslint/recommended', 39 | 40 | // Add rules for import/export syntax 41 | 'plugin:import/errors', 42 | 'plugin:import/warnings', 43 | 'plugin:import/typescript', 44 | 45 | // Add rules for Mocha-specific syntax 46 | 'plugin:mocha/recommended', 47 | 48 | // Add rules that specifically require type information using our tsconfig 49 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 50 | 51 | // Enable Prettier for ESLint --fix, and disable rules that conflict with Prettier 52 | 'prettier/@typescript-eslint', 53 | 'plugin:prettier/recommended', 54 | ], 55 | 56 | // rules: { 57 | // // This rule is about explicitly using `return undefined` when a function returns any non-undefined object. 58 | // // However, since we're using TypeScript, it will yell at us if a function is not allowed to return `undefined` in its signature, so we don't need this rule. 59 | // "consistent-return": "off", 60 | // }, 61 | 62 | overrides: [ 63 | // Overrides for all test files 64 | { 65 | files: 'test/**/*.ts', 66 | rules: { 67 | // For our Mocha test files, the pattern has been to have unnamed functions 68 | 'func-names': 'off', 69 | // Using non-null assertions (obj!.property) cancels the benefits of the strict null-checking mode, but these are test files, so we don't care. 70 | '@typescript-eslint/no-non-null-assertion': 'off', 71 | // For some test files, we shadow testing constants with function parameter names 72 | 'no-shadow': 'off', 73 | // Some of our test files declare helper classes with errors 74 | 'max-classes-per-file': 'off', 75 | }, 76 | }, 77 | { 78 | files: '**/*.ts', 79 | rules: { 80 | // Allow unused variables in our files when explicitly prepended with `_`. 81 | '@typescript-eslint/no-unused-vars': [ 82 | 'error', 83 | { argsIgnorePattern: '^_' }, 84 | ], 85 | 86 | // Allow us to import computed values for GRPC package definitions 87 | 'import/namespace': [2, { allowComputed: true }], 88 | 89 | // These rules are deprecated, but we have an old config that enables it 90 | '@typescript-eslint/camelcase': 'off', 91 | '@typescript-eslint/ban-ts-ignore': 'off', 92 | 93 | // These rules are actually disabled in @xpring-eng/eslint-config-base/loose at the moment 94 | '@typescript-eslint/no-unsafe-call': 'off', 95 | '@typescript-eslint/no-unsafe-member-access': 'off', 96 | '@typescript-eslint/no-unsafe-assignment': 'off', 97 | }, 98 | }, 99 | { 100 | files: ['src/XRP/default-xrp-client.ts'], 101 | rules: { 102 | // This is actually a good rule to have enabled, but for the XRPClient, we define a helper error message class in the same file 103 | 'max-classes-per-file': 'off', 104 | }, 105 | }, 106 | ], 107 | } 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore generated files. 2 | src/ILP/Generated/* 3 | src/XRP/Generated/* 4 | src/PayID/Generated/* 5 | scripts/.settings/* 6 | scripts/.classpath 7 | scripts/.project 8 | 9 | .idea/* 10 | # Ignore files compiled from typescript. 11 | build/* 12 | 13 | # Ignore files from VSCode. 14 | *.code-workspace 15 | scripts/.settings/* 16 | scripts/.classpath 17 | scripts/.project 18 | 19 | # Created by https://www.gitignore.io/api/osx,node 20 | # Edit at https://www.gitignore.io/?templates=osx,node 21 | 22 | ### Node ### 23 | # Logs 24 | logs 25 | *.log 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | lerna-debug.log* 30 | 31 | # Diagnostic reports (https://nodejs.org/api/report.html) 32 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 33 | 34 | # Runtime data 35 | pids 36 | *.pid 37 | *.seed 38 | *.pid.lock 39 | 40 | # Directory for instrumented libs generated by jscoverage/JSCover 41 | lib-cov 42 | 43 | # Coverage directory used by tools like istanbul 44 | coverage 45 | *.lcov 46 | 47 | # nyc test coverage 48 | .nyc_output 49 | 50 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 51 | .grunt 52 | 53 | # Bower dependency directory (https://bower.io/) 54 | bower_components 55 | 56 | # node-waf configuration 57 | .lock-wscript 58 | 59 | # Compiled binary addons (https://nodejs.org/api/addons.html) 60 | build/Release 61 | 62 | # Dependency directories 63 | node_modules/ 64 | jspm_packages/ 65 | 66 | # TypeScript v1 declaration files 67 | typings/ 68 | 69 | # TypeScript cache 70 | *.tsbuildinfo 71 | 72 | # Optional npm cache directory 73 | .npm 74 | 75 | # Optional eslint cache 76 | .eslintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # dotenv environment variables file 88 | .env 89 | .env.test 90 | 91 | # parcel-bundler cache (https://parceljs.org/) 92 | .cache 93 | 94 | # next.js build output 95 | .next 96 | 97 | # nuxt.js build output 98 | .nuxt 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # Serverless directories 104 | .serverless/ 105 | 106 | # FuseBox cache 107 | .fusebox/ 108 | 109 | # DynamoDB Local files 110 | .dynamodb/ 111 | 112 | ### OSX ### 113 | # General 114 | .DS_Store 115 | .AppleDouble 116 | .LSOverride 117 | 118 | # Icon must end with two \r 119 | Icon 120 | 121 | # Thumbnails 122 | ._* 123 | 124 | # Files that might appear in the root of a volume 125 | .DocumentRevisions-V100 126 | .fseventsd 127 | .Spotlight-V100 128 | .TemporaryItems 129 | .Trashes 130 | .VolumeIcon.icns 131 | .com.apple.timemachine.donotpresent 132 | 133 | # Directories potentially created on remote AFP share 134 | .AppleDB 135 | .AppleDesktop 136 | Network Trash Folder 137 | Temporary Items 138 | .apdisk 139 | 140 | # End of https://www.gitignore.io/api/osx,node 141 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - test 3 | 4 | .test: 5 | image: node:12 6 | stage: test 7 | before_script: 8 | # Install protoc 9 | - version=3.7.1 10 | - archive=protoc-$version-linux-x86_64 11 | - curl -O -L https://github.com/protocolbuffers/protobuf/releases/download/v$version/$archive.zip 12 | - unzip -d '/usr/local' $archive.zip 'bin/*' 'include/*' 13 | - rm -rf $archive.zip 14 | 15 | # Update java 16 | - apt-get update 17 | - wget https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/pool/main/a/adoptopenjdk-14-hotspot/adoptopenjdk-14-hotspot_14.0.0+36-2_amd64.deb 18 | - apt install -y ./adoptopenjdk-14-hotspot_14.0.0+36-2_amd64.deb 19 | 20 | # Install protoc plugin for grpc web 21 | - curl -L https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64 --output protoc-gen-grpc-web 22 | - mv protoc-gen-grpc-web /usr/local/bin/ 23 | - chmod +x /usr/local/bin/protoc-gen-grpc-web 24 | 25 | # Update submodules 26 | - git submodule update --init --recursive 27 | 28 | # Install deps 29 | - npm i --cache .npm --prefer-offline --no-audit --progress=false 30 | 31 | compile typescript: 32 | stage: test 33 | extends: .test 34 | script: 35 | - npm run build 36 | 37 | run tests: 38 | stage: test 39 | extends: .test 40 | script: 41 | - npx nyc npm test 42 | - mkdir -p coverage 43 | - npx nyc report --reporter=text-lcov > coverage/coverage.json 44 | - cat coverage/coverage.json 45 | - npx codecov 46 | 47 | run webpack: 48 | stage: test 49 | extends: .test 50 | script: 51 | - npm run webpack 52 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hermes-ilp"] 2 | path = hermes-ilp 3 | url = https://github.com/xpring-eng/hermes-ilp 4 | [submodule "rippled"] 5 | path = rippled 6 | url = http://github.com/ripple/rippled 7 | branch = develop 8 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | require: ['ts-node/register', 'source-map-support/register'], 5 | extension: ['ts'], 6 | spec: 'test/**/*.test.ts', 7 | 8 | // Do not look for mocha opts file 9 | opts: false, 10 | 11 | // Warn if test exceed 75ms duration 12 | slow: 75, 13 | 14 | // Fail if tests exceed 20000ms (20 sec) 15 | timeout: 20000, 16 | 17 | // Check for global variable leaks 18 | 'check-leaks': true, 19 | } 20 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build/**/* 2 | src/Generated/**/* 3 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | printWidth: 80, 4 | singleQuote: true, 5 | semi: false, 6 | trailingComma: "all", 7 | arrowParens: "always", 8 | }; 9 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "[javascript]": { 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "editor.formatOnSave": true 6 | }, 7 | "[typescript]": { 8 | "editor.defaultFormatter": "esbenp.prettier-vscode", 9 | "editor.formatOnSave": true 10 | }, 11 | "eslint.alwaysShowStatus": true, 12 | "eslint.lintTask.enable": true, 13 | "eslint.codeAction.showDocumentation": { 14 | "enable": true 15 | }, 16 | "editor.codeActionsOnSave": { 17 | "source.fixAll.eslint": true 18 | }, 19 | "files.insertFinalNewline": true, 20 | "files.trimFinalNewlines": true, 21 | "files.trimTrailingWhitespace": true, 22 | 23 | "mocha.files.glob": "test/**/*.test.ts", 24 | "mocha.requires": [ 25 | "ts-node/register" 26 | ], 27 | "java.configuration.updateBuildConfiguration": "disabled" 28 | } 29 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @amiecorso @tedkalaw @mvadari 2 | 3 | # Docs team owns markdown files 4 | *.md @ryangyoung 5 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of, but not limited to characteristics like 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. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of behavior that does not contribute to creating a positive environment include: 18 | 19 | * Using sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | 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. 28 | 29 | 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, harmful, or otherwise in violation of this Code of Conduct. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project email 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. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at . 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. 38 | 39 | 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. 40 | 41 | ## Attribution 42 | 43 | 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 44 | 45 | [homepage]: https://www.contributor-covenant.org 46 | 47 | For answers to common questions about this code of conduct, see 48 | https://www.contributor-covenant.org/faq 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for considering a contribution to [Xpring SDK](https://github.com/xpring-eng/xpring-sdk)! 4 | 5 | We're thrilled you're interested and your help is greatly appreciated. Contributing is a great way to learn about the [XRP Ledger](https://xrpl.org) and [Interledger Protocol (ILP)](https://interledger.org/). We are happy to review your pull requests. To make the process as smooth as possible, please read this document and follow the stated guidelines. 6 | 7 | ## About This Library 8 | 9 | Architecture Diagram of Xpring SDK 10 | 11 | Xpring-JS is a JavaScript library that is shipped as a consumable artifact in NPM. 12 | 13 | This library relies depends on both: 14 | 15 | - [Xpring Common JS](http://github.com/xpring-eng/xpring-common-js): Common code shared across Xpring SDK in JavaScript. 16 | - [Xpring Common Protocol Buffers](http://github.com/xpring-eng/xpring-common-protocol-buffers): Common protocol buffers shared across Xpring SDK. 17 | 18 | ## Requirements for a Successful Pull Request 19 | 20 | Before being considered for review or merging, each pull request must: 21 | 22 | - Pass continuous integration tests. 23 | - Update documentation for any new features. 24 | - Be free of lint errors. Please run `eslint` before sending a pull request. 25 | - Be [marked as drafts](https://github.blog/2019-02-14-introducing-draft-pull-requests/) until they are ready for review. 26 | - Adhere to the [code of conduct](CODE_OF_CONDUCT.md) for this repository. 27 | 28 | ## Dependencies 29 | 30 | A JDK installation (https://www.oracle.com/java/technologies/javase-jdk11-downloads.html). 31 | 32 | ## Building The Library 33 | The library should build and pass all tests. 34 | 35 | ```shell 36 | # Clone repository 37 | $ git clone https://github.com/xpring-eng/xpring-js.git 38 | $ cd xpring-js 39 | 40 | # Pull Submodules 41 | $ git submodule update --init --recursive 42 | 43 | # Install dependencies 44 | $ npm i 45 | 46 | # Install gRPC Web 47 | ## OSX, for web and node 48 | $ curl -L https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-darwin-x86_64 --output protoc-gen-grpc-web 49 | $ sudo mv protoc-gen-grpc-web /usr/local/bin/ 50 | $ chmod +x /usr/local/bin/protoc-gen-grpc-web 51 | 52 | $ curl -L https://github.com/grpc/grpc-node/releases/download/1.0.7/protoc-gen-grpc-node-1.0.7-darwin-x86_64 --output protoc-gen-grpc-node 53 | $ sudo mv protoc-gen-grpc-node /usr/local/bin/ 54 | $ chmod +x /usr/local/bin/protoc-gen-grpc-node 55 | 56 | ### Alternatively use Homebrew: 57 | $ brew install protobuf 58 | 59 | ## Linux, for web and node 60 | $ curl -L https://github.com/grpc/grpc-web/releases/download/1.0.7/protoc-gen-grpc-web-1.0.7-linux-x86_64 --output protoc-gen-grpc-web 61 | $ sudo mv protoc-gen-grpc-web /usr/local/bin/ 62 | $ chmod +x /usr/local/bin/protoc-gen-grpc-web 63 | 64 | $ curl -L https://github.com/grpc/grpc-node/releases/download/1.0.7/protoc-gen-grpc-node-1.0.7-darwin-x86_64 --output protoc-gen-grpc-node 65 | $ sudo mv protoc-gen-grpc-node /usr/local/bin/ 66 | $ chmod +x /usr/local/bin/protoc-gen-grpc-nod 67 | 68 | # Run tests. 69 | $ npm test 70 | ``` 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ripple Labs, All Rights Reserved 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xpring-eng/Xpring-JS/980303eae84ecaaa6c0a2014c679fa232dcbbbbe/architecture.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | target: 80% 6 | threshold: 1% 7 | base: auto 8 | if_ci_failed: error 9 | project: 10 | default: 11 | target: 80% 12 | threshold: 1% 13 | base: auto 14 | if_ci_failed: error 15 | -------------------------------------------------------------------------------- /openapitools.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json", 3 | "spaces": 2, 4 | "generator-cli": { 5 | "version": "4.3.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xpring-js", 3 | "version": "5.3.1", 4 | "description": "XpringJS provides a Javascript based SDK for interacting with the Ripple Ledger.", 5 | "main": "build/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/xpring-eng/Xpring-JS.git" 9 | }, 10 | "author": "Keefer Taylor | Xpring Engineering ", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/xpring-eng/Xpring-JS/issues" 14 | }, 15 | "files": [ 16 | "build/**/*" 17 | ], 18 | "scripts": { 19 | "build": "npm run clean && ./scripts/regenerate_protos.sh && ./scripts/regenerate_pay_id_api.sh && npm run lint && tsc -d && copyfiles -u 3 './src/XRP/Generated/**/*' ./build/XRP/Generated && copyfiles -u 3 './src/ILP/Generated/**/*' ./build/ILP/Generated", 20 | "clean": "rm -rf ./src/XRP/Generated ./src/ILP/Generated ./dist ./build ./src/PayID/Generated", 21 | "pretest": "npm run clean && ./scripts/regenerate_protos.sh && ./scripts/regenerate_pay_id_api.sh && npm run lint && tsc --noEmit", 22 | "lint": "eslint . --ext .ts --fix", 23 | "lintNoFix": "eslint . --ext .ts", 24 | "test": "nyc mocha", 25 | "prepublishOnly": "npm run test && npm run build", 26 | "webpack": "npm run clean && ./scripts/regenerate_protos.sh && ./scripts/regenerate_pay_id_api.sh && npm run lint && tsc --noEmit && webpack && copyfiles -u 3 './src/XRP/Generated/**/*' ./build/XRP/Generated && copyfiles -u 3 './src/ILP/Generated/**/*' ./build/ILP/Generated" 27 | }, 28 | "homepage": "https://github.com/xpring-eng/Xpring-JS#readme", 29 | "devDependencies": { 30 | "@babel/core": "^7.10.5", 31 | "@babel/plugin-proposal-class-properties": "^7.10.4", 32 | "@babel/plugin-transform-runtime": "^7.10.5", 33 | "@babel/preset-env": "^7.10.4", 34 | "@babel/preset-typescript": "^7.10.4", 35 | "@openapitools/openapi-generator-cli": "^2.0.2", 36 | "@types/chai": "4.2.14", 37 | "@types/google-protobuf": "^3.7.2", 38 | "@types/mocha": "8.2.0", 39 | "@types/node": "^14.0.1", 40 | "@typescript-eslint/eslint-plugin": "^4.3.0", 41 | "@typescript-eslint/parser": "^4.3.0", 42 | "babel-loader": "^8.1.0", 43 | "chai": "4.3.0", 44 | "codecov": "^3.7.1", 45 | "copyfiles": "^2.2.0", 46 | "eslint": "^7.2.0", 47 | "eslint-config-prettier": "^7.0.0", 48 | "eslint-plugin-import": "^2.21.1", 49 | "eslint-plugin-mocha": "^8.0.0", 50 | "eslint-plugin-prettier": "^3.1.3", 51 | "grpc-tools": "1.10.0", 52 | "grpc_tools_node_protoc_ts": "^5.0.0", 53 | "mocha": "^8.0.1", 54 | "nock": "^13.0.0", 55 | "nyc": "^15.1.0", 56 | "prettier": "^2.0.4", 57 | "source-map-support": "0.5.19", 58 | "ts-loader": "^8.0.1", 59 | "ts-node": "^9.0.0", 60 | "typescript": "^3.9.3", 61 | "webpack": "^5.4.0", 62 | "webpack-cli": "^4.0.0" 63 | }, 64 | "dependencies": { 65 | "@grpc/grpc-js": "^1.1.1", 66 | "assert": "^2.0.0", 67 | "axios": "^0.21.1", 68 | "big-integer": "^1.6.48", 69 | "bignumber.js": "^9.0.0", 70 | "browserify-zlib": "^0.2.0", 71 | "buffer": "^6.0.2", 72 | "crypto-browserify": "^3.12.0", 73 | "google-protobuf": "^3.12.2", 74 | "grpc-web": "1.2.1", 75 | "isomorphic-ws": "^4.0.1", 76 | "os-browserify": "^0.3.0", 77 | "stream-browserify": "^3.0.0", 78 | "url": "^0.11.0", 79 | "util": "^0.12.3", 80 | "ws": "^7.4.0", 81 | "xhr2": "0.2.1", 82 | "xpring-common-js": "6.2.9" 83 | }, 84 | "nyc": { 85 | "extension": [ 86 | ".ts", 87 | ".tsx" 88 | ], 89 | "include": [ 90 | "src/**/*.ts" 91 | ], 92 | "all": true, 93 | "check-leaks": true 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /scripts/init_mac.sh: -------------------------------------------------------------------------------- 1 | # Pull Submodules 2 | git submodule update --init --recursive 3 | 4 | # Install dependencies 5 | npm i 6 | brew update 7 | brew tap adoptopenjdk/openjdk 8 | brew cask install adoptopenjdk11 9 | brew install protobuf 10 | -------------------------------------------------------------------------------- /scripts/regenerate_pay_id_api.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | # Folder to place the generated classes in. 6 | DESTINATION_FOLDER=./src/PayID/Generated/ 7 | 8 | # Open API Specfication to use for generation. 9 | PAY_ID_OPEN_API_SPEC=./pay-id-api-spec/pay-id.v1.yml 10 | 11 | # Temporary directory to write files to. 12 | TMP_SWAGGER_DIR=./.tmp_swagger 13 | 14 | # Language to generate. 15 | LANG=typescript-axios 16 | 17 | # Folder containing generated sources. 18 | GENERATED_SOURCES_FOLDER=$TMP_SWAGGER_DIR/ 19 | 20 | ########################################################################## 21 | # Remove any stale files which are already generated. 22 | ########################################################################## 23 | echo "Removing stale files" 24 | rm -rf $DESTINATION_FOLDER 25 | echo "Done removing stale files" 26 | 27 | ########################################################################## 28 | # Regenerate Swagger API 29 | ########################################################################## 30 | 31 | echo "Regenerating Swagger API from PayID Open API Spec" 32 | 33 | mkdir -p $TMP_SWAGGER_DIR 34 | mkdir -p $DESTINATION_FOLDER 35 | 36 | npx @openapitools/openapi-generator-cli generate -i $PAY_ID_OPEN_API_SPEC -g $LANG -o $TMP_SWAGGER_DIR 37 | echo "Swagger Generation Complete!" 38 | 39 | ########################################################################## 40 | # Copy artifacts and remove excess files 41 | # 42 | # Swagger outputs a lot of helper boilerplate by default which we don't need. 43 | # Copy the source files to the directory and then delete the remainder. 44 | # 45 | # TODO(keefertaylor): There's probably a better way to do this. Investigate. 46 | ########################################################################## 47 | 48 | echo "Cleaning Up...." 49 | 50 | # Copy Source files 51 | cp -r $GENERATED_SOURCES_FOLDER/* $DESTINATION_FOLDER 52 | # Remove everything else. 53 | rm -rf $TMP_SWAGGER_DIR 54 | 55 | echo "Done Cleaning Up" 56 | echo "All Done!" 57 | -------------------------------------------------------------------------------- /scripts/regenerate_protos.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | ########################################################################## 6 | # Generate Protocol Buffers from Rippled. 7 | ########################################################################## 8 | 9 | echo "Generating Protocol Buffers from Rippled" 10 | 11 | # Directory to write generated code to (.js and .d.ts files) 12 | XRP_OUT_DIR_WEB="./src/XRP/Generated/web" 13 | XRP_OUT_DIR_NODE="./src/XRP/Generated/node" 14 | 15 | PROTO_PATH="./rippled/src/ripple/proto/" 16 | PROTO_SRC_FILES=$PROTO_PATH/org/xrpl/rpc/v1/*.proto 17 | 18 | mkdir -p $XRP_OUT_DIR_WEB 19 | mkdir -p $XRP_OUT_DIR_NODE 20 | 21 | # Generate web code. 22 | $PWD/node_modules/grpc-tools/bin/protoc \ 23 | --js_out=import_style=commonjs,binary:$XRP_OUT_DIR_WEB \ 24 | --grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:$XRP_OUT_DIR_WEB \ 25 | --proto_path $PROTO_PATH \ 26 | $PROTO_SRC_FILES 27 | 28 | # Generate node code. 29 | npx grpc_tools_node_protoc \ 30 | --js_out=import_style=commonjs,binary:$XRP_OUT_DIR_NODE \ 31 | --grpc_out=generate_package_definition:$XRP_OUT_DIR_NODE \ 32 | -I $PROTO_PATH \ 33 | $PROTO_SRC_FILES 34 | 35 | # Generate node typescript declaration files. 36 | npx protoc \ 37 | --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \ 38 | --ts_out=generate_package_definition:$XRP_OUT_DIR_NODE \ 39 | -I $PROTO_PATH \ 40 | $PROTO_SRC_FILES 41 | 42 | ########################################################################## 43 | # Generate Protocol Buffers from hermes-ilp. 44 | ########################################################################## 45 | 46 | echo "Generating Protocol Buffers from hermes-ilp" 47 | 48 | # Directory to write generated code to (.js and .d.ts files) 49 | ILP_OUT_DIR_WEB="./src/ILP/Generated/web" 50 | ILP_OUT_DIR_NODE="./src/ILP/Generated/node" 51 | 52 | mkdir -p $ILP_OUT_DIR_WEB 53 | mkdir -p $ILP_OUT_DIR_NODE 54 | 55 | # Generate web code. 56 | $PWD/node_modules/grpc-tools/bin/protoc \ 57 | --js_out=import_style=commonjs,binary:$ILP_OUT_DIR_WEB \ 58 | --grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext:$ILP_OUT_DIR_WEB \ 59 | --proto_path=$PWD/hermes-ilp/protocol-buffers/proto \ 60 | $PWD/hermes-ilp/protocol-buffers/proto/*.proto 61 | 62 | # Generate node code. 63 | npx grpc_tools_node_protoc \ 64 | --js_out=import_style=commonjs,binary:$ILP_OUT_DIR_NODE \ 65 | --grpc_out=generate_package_definition:$ILP_OUT_DIR_NODE \ 66 | -I $PWD/hermes-ilp/protocol-buffers/proto \ 67 | $PWD/hermes-ilp/protocol-buffers/proto/*.proto 68 | 69 | # Generate node typescript declaration files. 70 | npx protoc \ 71 | --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \ 72 | --ts_out=generate_package_definition:$ILP_OUT_DIR_NODE \ 73 | -I $PWD/hermes-ilp/protocol-buffers/proto \ 74 | $PWD/hermes-ilp/protocol-buffers/proto/*.proto 75 | 76 | echo "All done!" 77 | -------------------------------------------------------------------------------- /src/Common/utils.ts: -------------------------------------------------------------------------------- 1 | import { Utils } from 'xpring-common-js' 2 | 3 | const isNode = (): boolean => { 4 | return process?.release?.name === 'node' 5 | } 6 | 7 | /** 8 | * Converts a string such that each of its characters are represented as hex. 9 | * 10 | * @param value - the string to convert to hex. 11 | * @returns the hex encoded version of the string. 12 | */ 13 | export const stringToHex = (value: string): string => { 14 | return Buffer.from(value).toString('hex') 15 | } 16 | 17 | /** 18 | * Converts a string that is optionally in hex format into a Uint8Array. 19 | * 20 | * @param value - the string to convert to a Uint8Array. 21 | * @param isHex - flag to indicate it's a hex string or not. 22 | * @returns the Uint8Array value. 23 | */ 24 | export const stringToUint8Array = ( 25 | value?: string, 26 | isHex?: boolean, 27 | ): Uint8Array | undefined => { 28 | if (value?.length === 0) { 29 | return new Uint8Array() 30 | } 31 | return value && !isHex 32 | ? Utils.toBytes(stringToHex(value)) 33 | : (value && Utils.toBytes(value)) || undefined 34 | } 35 | 36 | export default isNode 37 | -------------------------------------------------------------------------------- /src/ILP/auth/ilp-credentials.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from '@grpc/grpc-js' 2 | import IlpError from '../ilp-error' 3 | 4 | /** 5 | * An extension of grpc.Metadata which provides a convenient way to 6 | * add an Authorization metadata header, and ensures every bearer token 7 | * going over the wire is prefixed with 'Bearer ' 8 | */ 9 | class IlpCredentials extends Metadata { 10 | private static BEARER_PREFIX = 'Bearer ' 11 | 12 | /** 13 | * Static initializer, which constructs a new IlpCredentials object and adds 14 | * an Authorization entry. If token is undefined, an Authorization header will 15 | * still be added, but the call will ultimately fail as Unauthorized 16 | * 17 | * @param token an optional access token to be added to IlpCredentials 18 | * @return a new instance of IlpCredentials, with an Authorization header 19 | */ 20 | public static build(token?: string): IlpCredentials { 21 | if (token && token.startsWith(this.BEARER_PREFIX)) { 22 | throw IlpError.invalidAccessToken 23 | } 24 | 25 | const credentials = new IlpCredentials() 26 | credentials.add('Authorization', this.BEARER_PREFIX.concat(token || '')) 27 | return credentials 28 | } 29 | } 30 | 31 | export default IlpCredentials 32 | -------------------------------------------------------------------------------- /src/ILP/auth/ilp-credentials.web.ts: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'grpc-web' 2 | import IlpError from '../ilp-error' 3 | 4 | /** 5 | * An extension of grpc-web.Metadata which provides a convenient way to 6 | * add an Authorization metadata header, and ensures every bearer token 7 | * going over the wire is prefixed with 'Bearer ' 8 | */ 9 | class IlpCredentials implements Metadata { 10 | [s: string]: string 11 | 12 | private static BEARER_PREFIX = 'Bearer ' 13 | 14 | /** 15 | * Static initializer, which constructs a new IlpCredentials object and adds 16 | * an Authorization entry. If token is undefined, this builder will return 17 | * undefined, so no metadata will be passed to a network call. This allows applications 18 | * in the browser to rely on HTTP cookies to provide authentication. 19 | * 20 | * @param token an optional bearer token to be added to IlpCredentials 21 | * @return a new instance of IlpCredentials, with an Authorization header if token is defined, 22 | * otherwise returns undefined 23 | */ 24 | public static build(token?: string): IlpCredentials | undefined { 25 | if (token && token.startsWith(this.BEARER_PREFIX)) { 26 | throw IlpError.invalidAccessToken 27 | } 28 | return token 29 | ? { Authorization: this.BEARER_PREFIX.concat(token) } 30 | : undefined 31 | } 32 | } 33 | 34 | export default IlpCredentials 35 | -------------------------------------------------------------------------------- /src/ILP/default-ilp-client.ts: -------------------------------------------------------------------------------- 1 | import { IlpClientDecorator } from './ilp-client-decorator' 2 | import isNode from '../Common/utils' 3 | import { IlpNetworkClient } from './ilp-network-client' 4 | import GrpcIlpNetworkClient from './grpc-ilp-network-client' 5 | import GrpcIlpNetworkClientWeb from './grpc-ilp-network-client.web' 6 | import { AccountBalance } from './model/account-balance' 7 | import { PaymentResult } from './model/payment-result' 8 | import { PaymentRequest } from './model/payment-request' 9 | import IlpError from './ilp-error' 10 | 11 | class DefaultIlpClient implements IlpClientDecorator { 12 | /** 13 | * Create a new DefaultIlpClient. 14 | * 15 | * The DefaultIlpClient will use gRPC to communicate with the given endpoint. 16 | * 17 | * @param grpcURL The URL of the gRPC instance to connect to. 18 | * @param forceWeb If `true`, then we will use the gRPC-Web client even when on Node. Defaults to false. 19 | * This is mainly for testing and in the future will be removed when we have browser testing. 20 | */ 21 | public static defaultIlpClientWithEndpoint( 22 | grpcURL: string, 23 | forceWeb = false, 24 | ): DefaultIlpClient { 25 | return isNode() && !forceWeb 26 | ? new DefaultIlpClient(new GrpcIlpNetworkClient(grpcURL)) 27 | : new DefaultIlpClient(new GrpcIlpNetworkClientWeb(grpcURL)) 28 | } 29 | 30 | /** 31 | * This constructor is meant solely for testing purposes. Users should prefer 32 | * DefaultIlpclient.defaultIlpClientWithEndpoint instead. 33 | * 34 | * @param networkClient A {@link IlpNetworkClient} which can make network calls to ILP infrastructure 35 | */ 36 | public constructor(private readonly networkClient: IlpNetworkClient) {} 37 | 38 | /** 39 | * Get the balance of the specified account on the connector. 40 | * 41 | * @param accountId The account ID to get the balance for. 42 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 43 | * it will be picked up from a cookie. 44 | * @return A Promise with balance information of the specified account 45 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 46 | */ 47 | public async getBalance( 48 | accountId: string, 49 | accessToken?: string, 50 | ): Promise { 51 | const request = this.networkClient.GetBalanceRequest() 52 | request.setAccountId(accountId) 53 | return this.networkClient 54 | .getBalance(request, accessToken) 55 | .catch((error) => { 56 | throw IlpError.from(error) 57 | }) 58 | .then((response) => AccountBalance.from(response)) 59 | } 60 | 61 | /** 62 | * Send a payment from the given accountId to the destinationPaymentPointer payment pointer 63 | * 64 | * @param paymentRequest A PaymentRequest with options for sending a payment 65 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 66 | * it will be picked up from a cookie. 67 | * @returns A promise which resolves to a `PaymentResult` of the original amount, the amount sent 68 | * in the senders denomination, and the amount that was delivered to the recipient in their denomination, as 69 | * well as if the payment was successful 70 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 71 | */ 72 | public async sendPayment( 73 | paymentRequest: PaymentRequest, 74 | accessToken?: string, 75 | ): Promise { 76 | const request = this.networkClient.SendPaymentRequest() 77 | request.setAmount(paymentRequest.amount.toJSNumber()) 78 | request.setDestinationPaymentPointer( 79 | paymentRequest.destinationPaymentPointer, 80 | ) 81 | request.setAccountId(paymentRequest.senderAccountId) 82 | return this.networkClient 83 | .send(request, accessToken) 84 | .catch((error) => { 85 | throw IlpError.from(error) 86 | }) 87 | .then((response) => PaymentResult.from(response)) 88 | } 89 | } 90 | 91 | export default DefaultIlpClient 92 | -------------------------------------------------------------------------------- /src/ILP/grpc-ilp-network-client.ts: -------------------------------------------------------------------------------- 1 | import * as grpc from '@grpc/grpc-js' 2 | import { GetBalanceResponse } from './Generated/node/get_balance_response_pb' 3 | import { GetBalanceRequest } from './Generated/node/get_balance_request_pb' 4 | import { SendPaymentRequest } from './Generated/node/send_payment_request_pb' 5 | import { SendPaymentResponse } from './Generated/node/send_payment_response_pb' 6 | import * as BalanceGrpcPb from './Generated/node/balance_service_grpc_pb' 7 | import * as ILPGrpcPb from './Generated/node/ilp_over_http_service_grpc_pb' 8 | import { IlpNetworkClient } from './ilp-network-client' 9 | import IlpCredentials from './auth/ilp-credentials' 10 | import isNode from '../Common/utils' 11 | 12 | class GrpcIlpNetworkClient implements IlpNetworkClient { 13 | private readonly balanceClient: BalanceGrpcPb.BalanceServiceClient 14 | 15 | private readonly paymentClient: ILPGrpcPb.IlpOverHttpServiceClient 16 | 17 | public constructor(grpcURL: string) { 18 | if (!isNode()) 19 | throw new Error('Use ILP-gRPC-Web Network Client on the browser!') 20 | 21 | const BalanceServiceClient = grpc.makeClientConstructor( 22 | BalanceGrpcPb['org.interledger.stream.proto.BalanceService'], 23 | 'BalanceService', 24 | ) 25 | this.balanceClient = (new BalanceServiceClient( 26 | grpcURL, 27 | grpc.credentials.createSsl(), 28 | ) as unknown) as BalanceGrpcPb.BalanceServiceClient 29 | 30 | const IlpOverHttpServiceClient = grpc.makeClientConstructor( 31 | ILPGrpcPb['org.interledger.stream.proto.IlpOverHttpService'], 32 | 'IlpOverHttpService', 33 | ) 34 | this.paymentClient = (new IlpOverHttpServiceClient( 35 | grpcURL, 36 | grpc.credentials.createSsl(), 37 | ) as unknown) as ILPGrpcPb.IlpOverHttpServiceClient 38 | } 39 | 40 | getBalance( 41 | request: GetBalanceRequest, 42 | accessToken?: string, 43 | ): Promise { 44 | return new Promise((resolve, reject): void => { 45 | this.balanceClient.getBalance( 46 | request, 47 | IlpCredentials.build(accessToken), 48 | (error, response) => { 49 | if (error || !response) { 50 | reject(error) 51 | return 52 | } 53 | resolve(response) 54 | }, 55 | ) 56 | }) 57 | } 58 | 59 | send( 60 | request: SendPaymentRequest, 61 | accessToken?: string, 62 | ): Promise { 63 | return new Promise((resolve, reject): void => { 64 | this.paymentClient.sendMoney( 65 | request, 66 | IlpCredentials.build(accessToken), 67 | (error, response) => { 68 | if (error || !response) { 69 | reject(error) 70 | return 71 | } 72 | resolve(response) 73 | }, 74 | ) 75 | }) 76 | } 77 | 78 | /* eslint-disable class-methods-use-this */ 79 | public SendPaymentRequest(): SendPaymentRequest { 80 | return new SendPaymentRequest() 81 | } 82 | 83 | public GetBalanceRequest(): GetBalanceRequest { 84 | return new GetBalanceRequest() 85 | } 86 | /* eslint-enable class-methods-use-this */ 87 | } 88 | 89 | export default GrpcIlpNetworkClient 90 | -------------------------------------------------------------------------------- /src/ILP/grpc-ilp-network-client.web.ts: -------------------------------------------------------------------------------- 1 | import { GetBalanceRequest } from './Generated/web/get_balance_request_pb' 2 | import { GetBalanceResponse } from './Generated/web/get_balance_response_pb' 3 | import { SendPaymentRequest } from './Generated/web/send_payment_request_pb' 4 | import { SendPaymentResponse } from './Generated/web/send_payment_response_pb' 5 | import { BalanceServiceClient } from './Generated/web/balance_service_grpc_web_pb' 6 | import { IlpOverHttpServiceClient } from './Generated/web/ilp_over_http_service_grpc_web_pb' 7 | import isNode from '../Common/utils' 8 | import { IlpNetworkClient } from './ilp-network-client' 9 | import IlpCredentials from './auth/ilp-credentials.web' 10 | 11 | class GrpcIlpNetworkClientWeb implements IlpNetworkClient { 12 | private readonly balanceClient: BalanceServiceClient 13 | 14 | private readonly paymentClient: IlpOverHttpServiceClient 15 | 16 | public constructor(grpcURL: string) { 17 | if (isNode()) { 18 | try { 19 | // This polyfill hack enables XMLHttpRequest on the global node.js state 20 | global.XMLHttpRequest = require('xhr2') // eslint-disable-line 21 | } catch { 22 | // Swallow the error here for browsers 23 | } 24 | } 25 | 26 | this.balanceClient = new BalanceServiceClient(grpcURL, null, { 27 | withCredentials: 'true', 28 | }) 29 | this.paymentClient = new IlpOverHttpServiceClient(grpcURL, null, { 30 | withCredentials: 'true', 31 | }) 32 | } 33 | 34 | getBalance( 35 | request: GetBalanceRequest, 36 | accessToken?: string, 37 | ): Promise { 38 | return new Promise((resolve, reject): void => { 39 | this.balanceClient.getBalance( 40 | request, 41 | IlpCredentials.build(accessToken), 42 | (error, response) => { 43 | if (error || !response) { 44 | reject(error) 45 | return 46 | } 47 | resolve(response) 48 | }, 49 | ) 50 | }) 51 | } 52 | 53 | send( 54 | request: SendPaymentRequest, 55 | accessToken?: string, 56 | ): Promise { 57 | return new Promise((resolve, reject): void => { 58 | this.paymentClient.sendMoney( 59 | request, 60 | IlpCredentials.build(accessToken), 61 | (error, response) => { 62 | if (error || !response) { 63 | reject(error) 64 | return 65 | } 66 | resolve(response) 67 | }, 68 | ) 69 | }) 70 | } 71 | 72 | /* eslint-disable class-methods-use-this */ 73 | public SendPaymentRequest(): SendPaymentRequest { 74 | return new SendPaymentRequest() 75 | } 76 | 77 | public GetBalanceRequest(): GetBalanceRequest { 78 | return new GetBalanceRequest() 79 | } 80 | /* eslint-enable class-methods-use-this */ 81 | } 82 | 83 | export default GrpcIlpNetworkClientWeb 84 | -------------------------------------------------------------------------------- /src/ILP/ilp-client-decorator.ts: -------------------------------------------------------------------------------- 1 | import { AccountBalance } from './model/account-balance' 2 | import { PaymentResult } from './model/payment-result' 3 | import { PaymentRequest } from './model/payment-request' 4 | 5 | export interface IlpClientDecorator { 6 | /** 7 | * Get the balance of the specified account on the connector. 8 | * 9 | * @param accountId The account ID to get the balance for. 10 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 11 | * it will be picked up from a cookie. 12 | * @return A Promise with balance information of the specified account 13 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 14 | */ 15 | getBalance(accountId: string, accessToken?: string): Promise 16 | 17 | /** 18 | * Send a payment from the given accountId to the destinationPaymentPointer payment pointer 19 | * 20 | * @param paymentRequest A PaymentRequest with options for sending a payment 21 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 22 | * it will be picked up from a cookie. 23 | * @returns A promise which resolves to a `PaymentResult` of the original amount, the amount sent 24 | * in the senders denomination, and the amount that was delivered to the recipient in their denomination, as 25 | * well as if the payment was successful 26 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 27 | */ 28 | sendPayment( 29 | paymentRequest: PaymentRequest, 30 | accessToken?: string, 31 | ): Promise 32 | } 33 | -------------------------------------------------------------------------------- /src/ILP/ilp-client.ts: -------------------------------------------------------------------------------- 1 | import { IlpClientDecorator } from './ilp-client-decorator' 2 | import DefaultIlpClient from './default-ilp-client' 3 | import { AccountBalance } from './model/account-balance' 4 | import { PaymentResult } from './model/payment-result' 5 | import { PaymentRequest } from './model/payment-request' 6 | 7 | class IlpClient { 8 | private readonly decoratedClient: IlpClientDecorator 9 | 10 | public constructor(grpcURL: string, forceWeb = false) { 11 | this.decoratedClient = DefaultIlpClient.defaultIlpClientWithEndpoint( 12 | grpcURL, 13 | forceWeb, 14 | ) 15 | } 16 | 17 | /** 18 | * Get the balance of the specified account on the connector. 19 | * 20 | * @param accountId The account ID to get the balance for. 21 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 22 | * it will be picked up from a cookie. 23 | * @return A Promise with balance information of the specified account 24 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 25 | */ 26 | public async getBalance( 27 | accountId: string, 28 | accessToken?: string, 29 | ): Promise { 30 | return this.decoratedClient.getBalance(accountId, accessToken) 31 | } 32 | 33 | /** 34 | * Send a payment from the given accountId to the destinationPaymentPointer payment pointer 35 | * 36 | * @param paymentRequest A PaymentRequest with options for sending a payment 37 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 38 | * it will be picked up from a cookie. 39 | * @returns A promise which resolves to a `PaymentResult` of the original amount, the amount sent 40 | * in the senders denomination, and the amount that was delivered to the recipient in their denomination, as 41 | * well as if the payment was successful 42 | * @throws an IlpError if the inputs were invalid or an error occurs during the call. 43 | */ 44 | public async sendPayment( 45 | paymentRequest: PaymentRequest, 46 | accessToken?: string, 47 | ): Promise { 48 | return this.decoratedClient.sendPayment(paymentRequest, accessToken) 49 | } 50 | } 51 | 52 | export default IlpClient 53 | -------------------------------------------------------------------------------- /src/ILP/ilp-error.ts: -------------------------------------------------------------------------------- 1 | import { Error as grpcWebError, StatusCode as grpcStatusCode } from 'grpc-web' 2 | 3 | /** 4 | * Types of errors that originate from ILP. 5 | */ 6 | export enum IlpErrorType { 7 | AccountNotFound, 8 | Internal, 9 | InvalidAccessToken, 10 | InvalidArgument, 11 | Unauthenticated, 12 | Unknown, 13 | } 14 | 15 | /** 16 | * Represents errors thrown by ILP components of the Xpring SDK. 17 | */ 18 | export default class IlpError extends Error { 19 | /** 20 | * Default errors. 21 | */ 22 | public static accountNotFound = new IlpError( 23 | IlpErrorType.AccountNotFound, 24 | 'Account not found.', 25 | ) 26 | 27 | public static internal = new IlpError( 28 | IlpErrorType.Internal, 29 | 'Internal error occurred on ILP network.', 30 | ) 31 | 32 | public static invalidAccessToken = new IlpError( 33 | IlpErrorType.InvalidAccessToken, 34 | 'Access token should not start with "Bearer "', 35 | ) 36 | 37 | public static invalidArgument = new IlpError( 38 | IlpErrorType.InvalidArgument, 39 | 'Invalid argument in request body.', 40 | ) 41 | 42 | public static unauthenticated = new IlpError( 43 | IlpErrorType.Unauthenticated, 44 | 'Authentication failed.', 45 | ) 46 | 47 | public static unknown: IlpError = new IlpError( 48 | IlpErrorType.Unknown, 49 | 'Unknown error occurred.', 50 | ) 51 | 52 | /** 53 | * Public constructor. 54 | * 55 | * @param errorType The type of error. 56 | * @param message The error message. 57 | */ 58 | public constructor( 59 | public readonly errorType: IlpErrorType, 60 | message: string | undefined = undefined, 61 | ) { 62 | super(message) 63 | } 64 | 65 | /** 66 | * Handle an Error thrown from an Ilp network client call by translating it to 67 | * a IlpError. 68 | * 69 | * gRPC services follow return an error with a status code, so we need to map gRPC error status 70 | * to native IlpErrors. GrpcNetworkClient and GrpcNetworkClientWeb also sometimes throw 71 | * a IlpError, so we need to handle that case in here as well. 72 | * 73 | * @param error Any error returned by a network call. 74 | * @return A {@link IlpError} that has been translated from a gRPC error, or which should be rethrown 75 | */ 76 | public static from(error: grpcWebError | IlpError): IlpError { 77 | if ('code' in error) { 78 | switch (error.code) { 79 | case grpcStatusCode.NOT_FOUND: 80 | return IlpError.accountNotFound 81 | case grpcStatusCode.UNAUTHENTICATED: 82 | return IlpError.unauthenticated 83 | case grpcStatusCode.INVALID_ARGUMENT: 84 | return IlpError.invalidArgument 85 | case grpcStatusCode.INTERNAL: 86 | return IlpError.internal 87 | default: 88 | return IlpError.unknown 89 | } 90 | } 91 | 92 | return error 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/ILP/ilp-network-client.ts: -------------------------------------------------------------------------------- 1 | import { GetBalanceRequest } from './Generated/web/get_balance_request_pb' 2 | import { GetBalanceResponse } from './Generated/web/get_balance_response_pb' 3 | import { SendPaymentRequest } from './Generated/web/send_payment_request_pb' 4 | import { SendPaymentResponse } from './Generated/web/send_payment_response_pb' 5 | 6 | export interface IlpNetworkClient { 7 | /** 8 | * Retrieve the balance for the given address. 9 | * 10 | * @param request the details required for fetching the balance 11 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 12 | * it will be picked up from a cookie. 13 | * @returns A {@link GetBalanceResponse} with balance information of the specified account 14 | */ 15 | getBalance( 16 | request: GetBalanceRequest, 17 | accessToken?: string, 18 | ): Promise 19 | 20 | /** 21 | * Send the given amount of XRP from the source wallet to the destination address. 22 | * 23 | * @param request the details of which account to send from, which payment pointer to receive to, and the amount to 24 | * send 25 | * @param accessToken Optional access token. If using node network client, accessToken must be supplied, otherwise 26 | * it will be picked up from a cookie. 27 | * @returns A promise which resolves to a `SendPaymentResponse` of the original amount, the amount sent 28 | * in the senders denomination, and the amount that was delivered to the recipient in their denomination, as 29 | * well as if the payment was successful 30 | */ 31 | send( 32 | request: SendPaymentRequest, 33 | accessToken?: string, 34 | ): Promise 35 | 36 | SendPaymentRequest(): SendPaymentRequest 37 | 38 | GetBalanceRequest(): GetBalanceRequest 39 | } 40 | -------------------------------------------------------------------------------- /src/ILP/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IlpClient } from './ilp-client' 2 | export { PaymentRequest, PaymentResult, AccountBalance } from './model' 3 | -------------------------------------------------------------------------------- /src/ILP/model/account-balance.ts: -------------------------------------------------------------------------------- 1 | import bigInt, { BigInteger } from 'big-integer' 2 | import { GetBalanceResponse } from '../Generated/web/get_balance_response_pb' 3 | 4 | /** 5 | * Response object for requests to get an account's balance 6 | */ 7 | export class AccountBalance { 8 | /** 9 | * The accountId for this account balance. 10 | */ 11 | readonly accountId: string 12 | 13 | /** 14 | * Currency code or other asset identifier that this account's balances will be denominated in 15 | */ 16 | readonly assetCode: string 17 | 18 | /** 19 | * Interledger amounts are integers, but most currencies are typically represented as # fractional units, e.g. cents. 20 | * This property defines how many Interledger units make # up one regular unit. For dollars, this would usually be set 21 | * to 9, so that Interledger # amounts are expressed in nano-dollars. 22 | * 23 | * This is an int representing this account's asset scale. 24 | */ 25 | readonly assetScale: number 26 | 27 | /** 28 | * The amount of units representing the clearing position this Connector operator holds with the account owner. A 29 | * positive clearing balance indicates the Connector operator has an outstanding liability (i.e., owes money) to the 30 | * account holder. A negative clearing balance represents an asset (i.e., the account holder owes money to the 31 | * operator). 32 | * 33 | * A BigInteger representing the net clearing balance of this account. 34 | */ 35 | readonly clearingBalance: BigInteger 36 | 37 | /** 38 | * The number of units that the account holder has prepaid. This value is factored into the value returned by 39 | * netBalance(), and is generally never negative. 40 | * 41 | * A BigInteger representing the number of units the counterparty (i.e., owner of this account) has 42 | * prepaid with this Connector. 43 | */ 44 | readonly prepaidAmount: BigInteger 45 | 46 | /** 47 | * The amount of units representing the aggregate position this Connector operator holds with the account owner. A 48 | * positive balance indicates the Connector operator has an outstanding liability (i.e., owes money) to the account 49 | * holder. A negative balance represents an asset (i.e., the account holder owes money to the operator). This value is 50 | * the sum of the clearing balance and the prepaid amount. 51 | * 52 | * A BigInteger representing the net clearingBalance of this account. 53 | */ 54 | readonly netBalance: BigInteger 55 | 56 | /** 57 | * Private constructor to initialize an AccountBalance 58 | */ 59 | constructor(options: { 60 | accountId: string 61 | assetCode: string 62 | assetScale: number 63 | clearingBalance: BigInteger 64 | prepaidAmount: BigInteger 65 | }) { 66 | this.accountId = options.accountId 67 | this.assetCode = options.assetCode 68 | this.assetScale = options.assetScale 69 | this.clearingBalance = options.clearingBalance 70 | this.prepaidAmount = options.prepaidAmount 71 | this.netBalance = options.clearingBalance.add(options.prepaidAmount) 72 | } 73 | 74 | /** 75 | * Constructs an AccountBalance from a GetBalanceResponse 76 | * 77 | * @param getBalanceResponse a GetBalanceResponse (protobuf object) whose field values will be used 78 | * to construct an AccountBalance 79 | * @return an AccountBalance with its fields set via the analogous protobuf fields. 80 | */ 81 | static from(getBalanceResponse: GetBalanceResponse): AccountBalance { 82 | return new AccountBalance({ 83 | accountId: getBalanceResponse.getAccountId(), 84 | assetCode: getBalanceResponse.getAssetCode(), 85 | assetScale: getBalanceResponse.getAssetScale(), 86 | clearingBalance: bigInt(getBalanceResponse.getClearingBalance()), 87 | prepaidAmount: bigInt(getBalanceResponse.getPrepaidAmount()), 88 | }) 89 | } 90 | } 91 | 92 | export default AccountBalance 93 | -------------------------------------------------------------------------------- /src/ILP/model/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PaymentRequest } from './payment-request' 2 | export { default as PaymentResult } from './payment-result' 3 | export { default as AccountBalance } from './account-balance' 4 | -------------------------------------------------------------------------------- /src/ILP/model/payment-request.ts: -------------------------------------------------------------------------------- 1 | import { BigInteger } from 'big-integer' 2 | 3 | /** 4 | * A request object that can be used to send a payment request to a connector 5 | */ 6 | export class PaymentRequest { 7 | /** 8 | * The amount to send. This amount is denominated in the asset code and asset scale of the sender's account 9 | * on the connector. For example, if the account has an asset code of "USD" and an asset scale of 9, 10 | * a payment request of 100 units would send 100 nano-dollars. 11 | */ 12 | readonly amount: BigInteger 13 | 14 | /** 15 | * A payment pointer is a standardized identifier for payment accounts. 16 | * This payment pointer will be the identifier for the account of the recipient of this payment on the ILP 17 | * network. 18 | * 19 | * @see "https://github.com/interledger/rfcs/blob/master/0026-payment-pointers/0026-payment-pointers.md" 20 | */ 21 | readonly destinationPaymentPointer: string 22 | 23 | /** 24 | * @return The accountID of the sender. 25 | */ 26 | readonly senderAccountId: string 27 | 28 | /** 29 | * Initialize a new instance of PaymentRequest 30 | */ 31 | public constructor(options: { 32 | amount: BigInteger 33 | destinationPaymentPointer: string 34 | senderAccountId: string 35 | }) { 36 | this.amount = options.amount 37 | this.destinationPaymentPointer = options.destinationPaymentPointer 38 | this.senderAccountId = options.senderAccountId 39 | } 40 | } 41 | 42 | export default PaymentRequest 43 | -------------------------------------------------------------------------------- /src/ILP/model/payment-result.ts: -------------------------------------------------------------------------------- 1 | import bigInt, { BigInteger } from 'big-integer' 2 | import { SendPaymentResponse } from '../Generated/web/send_payment_response_pb' 3 | 4 | /** 5 | * A response object containing details about a requested payment 6 | */ 7 | export class PaymentResult { 8 | /** 9 | * A BigInteger representing the original amount to be sent in a given payment. 10 | */ 11 | readonly originalAmount: BigInteger 12 | 13 | /** 14 | * The actual amount, in the receivers units, that was delivered to the receiver. Any currency conversion and/or 15 | * connector fees may cause this to be different than the amount sent. 16 | */ 17 | readonly amountDelivered: BigInteger 18 | 19 | /** 20 | * The actual amount, in the senders units, that was sent to the receiver. In the case of a timeout or rejected 21 | * packets this amount may be less than the requested amount to be sent. 22 | */ 23 | readonly amountSent: BigInteger 24 | 25 | /** 26 | * Indicates if the payment was completed successfully. 27 | * true if payment was successful 28 | */ 29 | readonly successfulPayment: boolean 30 | 31 | /** 32 | * Initializes a new PaymentResult 33 | */ 34 | constructor(options: { 35 | originalAmount: BigInteger 36 | amountDelivered: BigInteger 37 | amountSent: BigInteger 38 | successfulPayment: boolean 39 | }) { 40 | this.originalAmount = options.originalAmount 41 | this.amountDelivered = options.amountDelivered 42 | this.amountSent = options.amountSent 43 | this.successfulPayment = options.successfulPayment 44 | } 45 | 46 | /** 47 | * Constructs a PaymentResult from a protobuf SendPaymentResponse 48 | * 49 | * @param protoResponse a SendPaymentResponse to be converted 50 | * @return a PaymentResult with fields populated using the analogous fields in the proto object 51 | */ 52 | public static from(protoResponse: SendPaymentResponse): PaymentResult { 53 | return new PaymentResult({ 54 | originalAmount: bigInt(protoResponse.getOriginalAmount()), 55 | amountDelivered: bigInt(protoResponse.getAmountDelivered()), 56 | amountSent: bigInt(protoResponse.getAmountSent()), 57 | successfulPayment: protoResponse.getSuccessfulPayment(), 58 | }) 59 | } 60 | } 61 | 62 | export default PaymentResult 63 | -------------------------------------------------------------------------------- /src/PayID/pay-id-error.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | /** 4 | * Types of errors that originate from PayID. 5 | */ 6 | export enum PayIdErrorType { 7 | InvalidPayId, 8 | MappingNotFound, 9 | UnexpectedResponse, 10 | Unimplemented, 11 | Unknown, 12 | } 13 | 14 | /** 15 | * Represents errors thrown by PayID components of the Xpring SDK. 16 | */ 17 | export default class PayIdError extends Error { 18 | /** 19 | * Default errors. 20 | */ 21 | public static unimplemented = new PayIdError( 22 | PayIdErrorType.Unimplemented, 23 | 'Unimplemented', 24 | ) 25 | 26 | public static invalidPayId = new PayIdError( 27 | PayIdErrorType.InvalidPayId, 28 | 'Invalid payment pointer', 29 | ) 30 | 31 | /** 32 | * @param errorType The type of error. 33 | * @param message The error message. 34 | */ 35 | public constructor( 36 | public readonly errorType: PayIdErrorType, 37 | message: string | undefined = undefined, 38 | ) { 39 | super(message) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PayID/xrp-pay-id-client-interface.ts: -------------------------------------------------------------------------------- 1 | import { XrplNetwork } from 'xpring-common-js' 2 | 3 | export default interface XrpPayIdClientInterface { 4 | /** 5 | * @param network The network that addresses will be resolved on. 6 | */ 7 | xrplNetwork: XrplNetwork 8 | 9 | /** 10 | * Retrieve the XRP Address associated with a PayID. 11 | * 12 | * @note The returned value will always be in an X-Address format. 13 | * 14 | * @param payId The PayID to resolve for an address. 15 | * @returns An XRP address representing the given PayID. 16 | */ 17 | xrpAddressForPayId(payId: string): Promise 18 | } 19 | -------------------------------------------------------------------------------- /src/PayID/xrp-pay-id-client.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-classes-per-file */ 2 | 3 | import { XrplNetwork } from 'xpring-common-js' 4 | import XrpUtils from '../XRP/shared/xrp-utils' 5 | import PayIdClient from './pay-id-client' 6 | import PayIdError, { PayIdErrorType } from './pay-id-error' 7 | import XrpPayIdClientInterface from './xrp-pay-id-client-interface' 8 | 9 | /** 10 | * Provides functionality for XRP in the PayID protocol. 11 | */ 12 | export default class XrpPayIdClient 13 | extends PayIdClient 14 | implements XrpPayIdClientInterface { 15 | /** 16 | * @param xrplNetwork The XRP Ledger network that this client attaches to. 17 | * @param useHttps Whether to use HTTPS when making PayID requests. Most users should set this to 'true' to avoid 18 | * Man-in-the-Middle attacks. Exposed as an option for testing purposes. Defaults to true. 19 | */ 20 | constructor(public readonly xrplNetwork: XrplNetwork, useHttps = true) { 21 | super(useHttps) 22 | } 23 | 24 | /** 25 | * Retrieve the XRP address associated with a PayID. 26 | * 27 | * Note: Addresses are always in the X-Address format. 28 | * @see https://xrpaddress.info/ 29 | * 30 | * @param payId The PayID to resolve for an address. 31 | * @returns An XRP address representing the given PayID. 32 | */ 33 | async xrpAddressForPayId(payId: string): Promise { 34 | const result = await super.cryptoAddressForPayId( 35 | payId, 36 | `xrpl-${this.xrplNetwork}`, 37 | ) 38 | 39 | const { address } = result 40 | if (XrpUtils.isValidXAddress(address)) { 41 | return address 42 | } 43 | const isTest = this.xrplNetwork !== XrplNetwork.Main 44 | 45 | const tag = result.tag ? Number(result.tag) : undefined 46 | 47 | // Ensure if there was a tag attached that it could be parsed to a number. 48 | if (result.tag && tag === undefined) { 49 | throw new PayIdError( 50 | PayIdErrorType.UnexpectedResponse, 51 | 'The returned tag was in an unexpected format', 52 | ) 53 | } 54 | 55 | const encodedXAddress = XrpUtils.encodeXAddress(address, tag, isTest) 56 | if (!encodedXAddress) { 57 | throw new PayIdError( 58 | PayIdErrorType.UnexpectedResponse, 59 | 'The returned address was in an unexpected format', 60 | ) 61 | } 62 | return encodedXAddress 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/XRP/core-xrpl-client-interface.ts: -------------------------------------------------------------------------------- 1 | import { Wallet, XrplNetwork } from 'xpring-common-js' 2 | import TransactionResult from './shared/transaction-result' 3 | import RawTransactionStatus from './shared/raw-transaction-status' 4 | 5 | /** 6 | * An interface that CoreXrplClients adhere to. 7 | */ 8 | export default interface CoreXrplClientInterface { 9 | /** The XRPL Network of the node that this client is communicating with. */ 10 | network: XrplNetwork 11 | 12 | /** 13 | * The core logic of reliable submission. Polls the ledger until the result of the transaction 14 | * can be considered final, meaning it has either been included in a validated ledger, or the 15 | * transaction's lastLedgerSequence has been surpassed by the latest ledger sequence (meaning it 16 | * will never be included in a validated ledger.) 17 | * 18 | * @param transactionHash The hash of the transaction being awaited. 19 | * @param sender The address used to obtain the latest ledger sequence. 20 | */ 21 | waitForFinalTransactionOutcome( 22 | transactionHash: string, 23 | sender: Wallet, 24 | ): Promise<{ 25 | rawTransactionStatus: RawTransactionStatus 26 | lastLedgerPassed: boolean 27 | }> 28 | 29 | /** 30 | * Waits for a transaction to complete and returns a TransactionResult. 31 | * 32 | * @param transactionHash The transaction to wait for. 33 | * @param wallet The wallet sending the transaction. 34 | * 35 | * @returns A Promise resolving to a TransactionResult containing the results of the transaction associated with 36 | * the given transaction hash. 37 | */ 38 | getFinalTransactionResultAsync( 39 | transactionHash: string, 40 | wallet: Wallet, 41 | ): Promise 42 | } 43 | -------------------------------------------------------------------------------- /src/XRP/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PaymentFlags } from './shared/payment-flags' 2 | export { default as TransactionStatus } from './shared/transaction-status' 3 | export { default as XrpClient } from './xrp-client' 4 | export { default as XrpUtils } from './shared/xrp-utils' 5 | export { default as XrpError, XrpErrorType } from './shared/xrp-error' 6 | export { default as IssuedCurrencyClient } from './issued-currency-client' 7 | export { default as TrustLine } from './shared/trustline' 8 | export { default as GatewayBalances } from './shared/gateway-balances' 9 | // export { IssuedCurrency } from './shared/rippled-web-socket-schema' 10 | -------------------------------------------------------------------------------- /src/XRP/network-clients/grpc-network-client-interface.ts: -------------------------------------------------------------------------------- 1 | import { AccountAddress } from '../Generated/web/org/xrpl/rpc/v1/account_pb' 2 | import { 3 | SubmitTransactionRequest, 4 | SubmitTransactionResponse, 5 | } from '../Generated/web/org/xrpl/rpc/v1/submit_pb' 6 | import { 7 | GetAccountTransactionHistoryRequest, 8 | GetAccountTransactionHistoryResponse, 9 | } from '../Generated/web/org/xrpl/rpc/v1/get_account_transaction_history_pb' 10 | import { 11 | GetFeeRequest, 12 | GetFeeResponse, 13 | } from '../Generated/web/org/xrpl/rpc/v1/get_fee_pb' 14 | import { 15 | GetAccountInfoRequest, 16 | GetAccountInfoResponse, 17 | } from '../Generated/web/org/xrpl/rpc/v1/get_account_info_pb' 18 | import { 19 | GetTransactionRequest, 20 | GetTransactionResponse, 21 | } from '../Generated/web/org/xrpl/rpc/v1/get_transaction_pb' 22 | 23 | /** 24 | * The GrpcNetworkClientInterface provides a wrapper around gRPC network calls to a rippled node. 25 | */ 26 | export interface GrpcNetworkClientInterface { 27 | getAccountInfo( 28 | request: GetAccountInfoRequest, 29 | ): Promise 30 | getFee(request: GetFeeRequest): Promise 31 | getTransaction( 32 | request: GetTransactionRequest, 33 | ): Promise 34 | submitTransaction( 35 | request: SubmitTransactionRequest, 36 | ): Promise 37 | getTransactionHistory( 38 | request: GetAccountTransactionHistoryRequest, 39 | ): Promise 40 | 41 | AccountAddress(): AccountAddress 42 | GetAccountInfoRequest(): GetAccountInfoRequest 43 | GetTransactionRequest(): GetTransactionRequest 44 | GetFeeRequest(): GetFeeRequest 45 | SubmitTransactionRequest(): SubmitTransactionRequest 46 | GetAccountTransactionHistoryRequest(): GetAccountTransactionHistoryRequest 47 | } 48 | -------------------------------------------------------------------------------- /src/XRP/network-clients/grpc-xrp-network-client.web.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetAccountTransactionHistoryRequest, 3 | GetAccountTransactionHistoryResponse, 4 | } from '../Generated/web/org/xrpl/rpc/v1/get_account_transaction_history_pb' 5 | import { XRPLedgerAPIServiceClient } from '../Generated/web/org/xrpl/rpc/v1/xrp_ledger_grpc_web_pb' 6 | 7 | import { AccountAddress } from '../Generated/web/org/xrpl/rpc/v1/account_pb' 8 | import { 9 | SubmitTransactionRequest, 10 | SubmitTransactionResponse, 11 | } from '../Generated/web/org/xrpl/rpc/v1/submit_pb' 12 | import { 13 | GetTransactionRequest, 14 | GetTransactionResponse, 15 | } from '../Generated/web/org/xrpl/rpc/v1/get_transaction_pb' 16 | import { 17 | GetFeeRequest, 18 | GetFeeResponse, 19 | } from '../Generated/web/org/xrpl/rpc/v1/get_fee_pb' 20 | import { 21 | GetAccountInfoRequest, 22 | GetAccountInfoResponse, 23 | } from '../Generated/web/org/xrpl/rpc/v1/get_account_info_pb' 24 | import { GrpcNetworkClientInterface } from './grpc-network-client-interface' 25 | import isNode from '../../Common/utils' 26 | 27 | /** 28 | * A GRPC Based network client. 29 | */ 30 | export default class XrpGrpcNetworkClient 31 | implements GrpcNetworkClientInterface { 32 | private readonly grpcClient: XRPLedgerAPIServiceClient 33 | 34 | public constructor(grpcURL: string) { 35 | if (isNode()) { 36 | try { 37 | // This polyfill hack enables XMLHttpRequest on the global node.js state 38 | global.XMLHttpRequest = require('xhr2') // eslint-disable-line 39 | } catch { 40 | // Swallow the error here for browsers 41 | } 42 | } 43 | this.grpcClient = new XRPLedgerAPIServiceClient(grpcURL) 44 | } 45 | 46 | public async getAccountInfo( 47 | request: GetAccountInfoRequest, 48 | ): Promise { 49 | return new Promise((resolve, reject): void => { 50 | this.grpcClient.getAccountInfo(request, {}, (error, response): void => { 51 | if (error != null || response == null) { 52 | reject(error) 53 | return 54 | } 55 | resolve(response) 56 | }) 57 | }) 58 | } 59 | 60 | public async getFee(request: GetFeeRequest): Promise { 61 | return new Promise((resolve, reject): void => { 62 | this.grpcClient.getFee(request, {}, (error, response): void => { 63 | if (error != null || response == null) { 64 | reject(error) 65 | return 66 | } 67 | resolve(response) 68 | }) 69 | }) 70 | } 71 | 72 | public async getTransaction( 73 | request: GetTransactionRequest, 74 | ): Promise { 75 | return new Promise((resolve, reject): void => { 76 | this.grpcClient.getTransaction(request, {}, (error, response): void => { 77 | if (error != null || response == null) { 78 | reject(error) 79 | return 80 | } 81 | resolve(response) 82 | }) 83 | }) 84 | } 85 | 86 | public async submitTransaction( 87 | request: SubmitTransactionRequest, 88 | ): Promise { 89 | return new Promise((resolve, reject): void => { 90 | this.grpcClient.submitTransaction( 91 | request, 92 | {}, 93 | (error, response): void => { 94 | if (error != null || response == null) { 95 | reject(error) 96 | return 97 | } 98 | resolve(response) 99 | }, 100 | ) 101 | }) 102 | } 103 | 104 | public async getTransactionHistory( 105 | request: GetAccountTransactionHistoryRequest, 106 | ): Promise { 107 | return new Promise((resolve, reject): void => { 108 | this.grpcClient.getAccountTransactionHistory( 109 | request, 110 | {}, 111 | (error, response): void => { 112 | if (error != null || response == null) { 113 | reject(error) 114 | return 115 | } 116 | resolve(response) 117 | }, 118 | ) 119 | }) 120 | } 121 | 122 | /* eslint-disable class-methods-use-this */ 123 | public AccountAddress(): AccountAddress { 124 | return new AccountAddress() 125 | } 126 | 127 | public GetAccountInfoRequest(): GetAccountInfoRequest { 128 | return new GetAccountInfoRequest() 129 | } 130 | 131 | public GetTransactionRequest(): GetTransactionRequest { 132 | return new GetTransactionRequest() 133 | } 134 | 135 | public GetFeeRequest(): GetFeeRequest { 136 | return new GetFeeRequest() 137 | } 138 | 139 | public SubmitTransactionRequest(): SubmitTransactionRequest { 140 | return new SubmitTransactionRequest() 141 | } 142 | 143 | public GetAccountTransactionHistoryRequest(): GetAccountTransactionHistoryRequest { 144 | return new GetAccountTransactionHistoryRequest() 145 | } 146 | /* eslint-enable class-methods-use-this */ 147 | } 148 | -------------------------------------------------------------------------------- /src/XRP/network-clients/web-socket-network-client-interface.ts: -------------------------------------------------------------------------------- 1 | import IssuedCurrency from '../shared/issued-currency' 2 | import { 3 | StatusResponse, 4 | TransactionResponse, 5 | AccountLinesResponse, 6 | GatewayBalancesResponse, 7 | RipplePathFindResponse, 8 | SourceCurrency, 9 | } from '../shared/rippled-web-socket-schema' 10 | 11 | /** 12 | * The WebSocketNetworkClientInterface defines the calls available via the rippled WebSocket API. 13 | */ 14 | export interface WebSocketNetworkClientInterface { 15 | /** 16 | * Subscribes for notification about every validated transaction that affects the given account. 17 | * @see https://xrpl.org/monitor-incoming-payments-with-websocket.html 18 | * 19 | * @param account The account from which to subscribe to incoming transactions, encoded as a classic address. 20 | * @returns The response from the websocket confirming the subscription. 21 | */ 22 | subscribeToAccount( 23 | account: string, 24 | callback: (data: TransactionResponse) => void, 25 | ): Promise 26 | 27 | /** 28 | * Unsubscribes from notifications about every validated transaction that affects the given account. 29 | * @see https://xrpl.org/unsubscribe.html 30 | * 31 | * @param account The account from which to unsubscribe from incoming transactions, encoded as a classic address. 32 | * @returns The response from the websocket confirming the unsubscription. 33 | */ 34 | unsubscribeFromAccount(account: string): Promise 35 | 36 | /** 37 | * Submits an account_lines request to the rippled WebSocket API. 38 | * @see https://xrpl.org/account_lines.html 39 | * 40 | * @param account The address of the account to query for trust lines. 41 | * @param peerAccount (Optional) The address of a second account. If provided, show only trust lines connecting the two accounts. 42 | */ 43 | getAccountLines( 44 | account: string, 45 | peerAccount?: string, 46 | ): Promise 47 | 48 | /** 49 | * Submits a gateway_balances request to the rippled WebSocket API. 50 | * @see https://xrpl.org/gateway_balances.html 51 | * 52 | * @param account The XRPL account for which to retrieve issued currency balances. 53 | * @param addressesToExclude (Optional) An array of operational address to exclude from the balances issued. 54 | * @see https://xrpl.org/issuing-and-operational-addresses.html 55 | */ 56 | getGatewayBalances( 57 | account: string, 58 | addressesToExclude?: Array, 59 | ): Promise 60 | 61 | /** 62 | * Submits a ripple_path_find request to the rippled WebSocket API. 63 | * @see https://xrpl.org/ripple_path_find.html 64 | * 65 | * @param sourceAccount The XRPL account at the start of the desired path, as a classic address. 66 | * @param destinationAccount The XRPL account at the end of the desired path, as a classic address. 67 | * @param destinationAmount The currency amount that the destination account would receive in a transaction 68 | * (-1 if the path should deliver as much as possible). 69 | * @param sendMax The currency amount that would be spent in the transaction (cannot be used with sourceCurrencies). 70 | * @param sourceCurrencies An array of currencies that the source account might want to spend (cannot be used with sendMax). 71 | */ 72 | findRipplePath( 73 | sourceAccount: string, 74 | destinationAccount: string, 75 | destinationAmount: string | IssuedCurrency, 76 | sendMax?: string | IssuedCurrency, 77 | sourceCurrencies?: SourceCurrency[], 78 | ): Promise 79 | 80 | close(): void 81 | } 82 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/index.ts: -------------------------------------------------------------------------------- 1 | export { default as XrpAccountDelete } from './xrp-account-delete' 2 | export { default as XrpAccountSet } from './xrp-account-set' 3 | export { default as XrpCheckCancel } from './xrp-check-cancel' 4 | export { default as XrpCheckCreate } from './xrp-check-create' 5 | export { default as XrpCheckCash } from './xrp-check-cash' 6 | export { default as XrpCurrencyAmount } from './xrp-currency-amount' 7 | export { default as XrpCurrency } from './xrp-currency' 8 | export { default as XrpDepositPreauth } from './xrp-deposit-preauth' 9 | export { default as XrpEscrowCancel } from './xrp-escrow-cancel' 10 | export { default as XrpEscrowCreate } from './xrp-escrow-create' 11 | export { default as XrpEscrowFinish } from './xrp-escrow-finish' 12 | export { default as XrplIssuedCurrency } from './xrpl-issued-currency' 13 | export { default as XrpMemo } from './xrp-memo' 14 | export { default as XrpOfferCancel } from './xrp-offer-cancel' 15 | export { default as XrpOfferCreate } from './xrp-offer-create' 16 | export { default as XrpPathElement } from './xrp-path-element' 17 | export { default as XrpPath } from './xrp-path' 18 | export { default as XrpPaymentChannelClaim } from './xrp-payment-channel-claim' 19 | export { default as XrpPaymentChannelCreate } from './xrp-payment-channel-create' 20 | export { default as XrpPaymentChannelFund } from './xrp-payment-channel-fund' 21 | export { default as XrpPayment } from './xrp-payment' 22 | export { default as XrpSetRegularKey } from './xrp-set-regular-key' 23 | export { default as XrpSignerEntry } from './xrp-signer-entry' 24 | export { default as XrpSignerListSet } from './xrp-signer-list-set' 25 | export { default as XrpSigner } from './xrp-signer' 26 | export { default as XrpTransaction } from './xrp-transaction' 27 | export { default as XrpTrustSet } from './xrp-trust-set' 28 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-account-delete.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { AccountDelete } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | 6 | /* 7 | * Represents an AccountDelete transaction on the XRP Ledger. 8 | * 9 | * An AccountDelete transaction deletes an account and any objects it owns in the XRP Ledger, 10 | * if possible, sending the account's remaining XRP to a specified destination account. 11 | * 12 | * @see: https://xrpl.org/accountdelete.html 13 | */ 14 | export default class XrpAccountDelete { 15 | /** 16 | * Constructs an XrpAccountDelete from an AccountDelete protocol buffer. 17 | * 18 | * @param accountDelete an AccountDelete (protobuf object) whose field values will be used 19 | * to construct an XrpAccountDelete 20 | * @return an XrpAccountDelete with its fields set via the analogous protobuf fields. 21 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L118 22 | */ 23 | public static from( 24 | accountDelete: AccountDelete, 25 | xrplNetwork: XrplNetwork, 26 | ): XrpAccountDelete { 27 | const destination = accountDelete.getDestination()?.getValue()?.getAddress() 28 | if (!destination) { 29 | throw new XrpError( 30 | XrpErrorType.MalformedProtobuf, 31 | 'AccountDelete protobuf is missing `destination` field.', 32 | ) 33 | } 34 | const destinationTag = accountDelete.getDestinationTag()?.getValue() 35 | 36 | const destinationXAddress = XrpUtils.encodeXAddress( 37 | destination, 38 | destinationTag, 39 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 40 | ) 41 | if (!destinationXAddress) { 42 | throw new XrpError( 43 | XrpErrorType.MalformedProtobuf, 44 | 'Cannot construct XAddress from AccountDelete protobuf `destination` and `destinationTag` fields.', 45 | ) 46 | } 47 | return new XrpAccountDelete(destinationXAddress) 48 | } 49 | 50 | /** 51 | * @param destinationXAddress The address and destination tag of an account to receive any leftover XRP after deleting the 52 | * sending account, encoded as an X-address (see https://xrpaddress.info/). 53 | * Must be a funded account in the ledger, and must not be the sending account. 54 | */ 55 | private constructor(readonly destinationXAddress: string) {} 56 | } 57 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-account-set.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { AccountSet } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | 4 | /* 5 | * Represents an AccountSet transaction on the XRP Ledger. 6 | * 7 | * An AccountSet transaction modifies the properties of an account in the XRP Ledger. 8 | * 9 | * @see: https://xrpl.org/accountset.html 10 | */ 11 | export default class XrpAccountSet { 12 | /** 13 | * Constructs an XrpAccountSet from an AccountSet. 14 | * 15 | * @param accountSet an AccountSet (protobuf object) whose field values will be used 16 | * to construct an XrpAccountSet 17 | * @return an XrpAccountSet with its fields set via the analogous protobuf fields. 18 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L100 19 | */ 20 | public static from(accountSet: AccountSet): XrpAccountSet { 21 | const clearFlag = accountSet.getClearFlag()?.getValue() 22 | const domain = accountSet.getDomain()?.getValue() 23 | if (domain !== undefined && domain?.toLowerCase() !== domain) { 24 | throw new XrpError( 25 | XrpErrorType.MalformedProtobuf, 26 | 'AccountSet protobuf field `domain` is not lowercase.', 27 | ) 28 | } 29 | const emailHash = accountSet.getEmailHash()?.getValue_asU8() 30 | const messageKey = accountSet.getMessageKey()?.getValue_asU8() 31 | const setFlag = accountSet.getSetFlag()?.getValue() 32 | if ( 33 | clearFlag !== undefined && 34 | setFlag !== undefined && 35 | clearFlag === setFlag 36 | ) { 37 | throw new XrpError( 38 | XrpErrorType.MalformedProtobuf, 39 | 'AccountSet protobuf fields `clearFlag` and `setFlag` are equal.', 40 | ) 41 | } 42 | const transferRate = accountSet.getTransferRate()?.getValue() 43 | if (transferRate != undefined) { 44 | const maxTransferRate = 2000000000 45 | const minTransferRate = 1000000000 46 | const specialCaseTransferRate = 0 47 | if (transferRate > maxTransferRate) { 48 | throw new XrpError( 49 | XrpErrorType.MalformedProtobuf, 50 | `AccountSet protobuf field \`transferRate\` is above ${maxTransferRate}.`, 51 | ) 52 | } 53 | if ( 54 | transferRate < minTransferRate && 55 | transferRate !== specialCaseTransferRate 56 | ) { 57 | throw new XrpError( 58 | XrpErrorType.MalformedProtobuf, 59 | `AccountSet protobuf field \`transferRate\` is below ${minTransferRate}.`, 60 | ) 61 | } 62 | } 63 | const tickSize = accountSet.getTickSize()?.getValue() 64 | if (tickSize !== undefined && !this.isValidTickSize(tickSize)) { 65 | throw new XrpError( 66 | XrpErrorType.MalformedProtobuf, 67 | 'AccountSet protobuf field `tickSize` not between 3 and 15, inclusive, or 0.', 68 | ) 69 | } 70 | return new XrpAccountSet( 71 | clearFlag, 72 | domain, 73 | emailHash, 74 | messageKey, 75 | setFlag, 76 | transferRate, 77 | tickSize, 78 | ) 79 | } 80 | 81 | private static isValidTickSize(tickSize: number) { 82 | const minTickSize = 3 83 | const maxTickSize = 15 84 | const disableTickSize = 0 85 | return ( 86 | (minTickSize <= tickSize && tickSize <= maxTickSize) || 87 | tickSize === disableTickSize 88 | ) 89 | } 90 | 91 | /** 92 | * @param clearFlag (Optional) Unique identifier of a flag to disable for this account. 93 | * @param domain (Optional) The domain that owns this account, as a string of hex representing the ASCII for the domain in lowercase. 94 | * @param emailHash (Optional) Hash of an email address to be used for generating an avatar image. 95 | * @param messageKey (Optional) Public key for sending encrypted messages to this account. 96 | * @param setFlag (Optional) Integer flag to enable for this account. 97 | * @param transferRate (Optional) The fee to charge when users transfer this account's issued currencies, represented as billionths of a unit. 98 | * Cannot be more than 2000000000 or less than 1000000000, except for the special case 0 meaning no fee. 99 | * @param tickSize (Optional) Tick size to use for offers involving a currency issued by this address. 100 | * The exchange rates of those offers is rounded to this many significant digits. 101 | * Valid values are 3 to 15 inclusive, or 0 to disable. (Requires the TickSize amendment.) 102 | */ 103 | private constructor( 104 | readonly clearFlag?: number, 105 | readonly domain?: string, 106 | readonly emailHash?: Uint8Array, 107 | readonly messageKey?: Uint8Array, 108 | readonly setFlag?: number, 109 | readonly transferRate?: number, 110 | readonly tickSize?: number, 111 | ) {} 112 | } 113 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-check-cancel.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { CheckCancel } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | 4 | /* 5 | * Represents a CheckCancel transaction on the XRP Ledger. 6 | * 7 | * A CheckCancel transaction cancels an unredeemed Check, removing it from the ledger without sending any money. 8 | * The source or the destination of the check can cancel a Check at any time using this transaction type. 9 | * If the Check has expired, any address can cancel it. 10 | * 11 | * @see: https://xrpl.org/checkcancel.html 12 | */ 13 | export default class XrpCheckCancel { 14 | /** 15 | * Constructs an XrpCheckCancel from a CheckCancel protocol buffer. 16 | * 17 | * @param checkCancel a CheckCancel (protobuf object) whose field values will be used to construct an XrpCheckCancel 18 | * @return an XrpCheckCancel with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L126 20 | */ 21 | public static from(checkCancel: CheckCancel): XrpCheckCancel { 22 | const checkId = checkCancel.getCheckId()?.getValue_asB64() 23 | if (!checkId) { 24 | throw new XrpError( 25 | XrpErrorType.MalformedProtobuf, 26 | 'CheckCancel protobuf is missing `CheckID` field.', 27 | ) 28 | } 29 | return new XrpCheckCancel(checkId) 30 | } 31 | 32 | /** 33 | * @param checkId The ID of the Check ledger object to cancel, as a 64-character hexadecimal string. 34 | */ 35 | private constructor(readonly checkId: string) {} 36 | } 37 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-check-cash.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { CheckCash } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrencyAmount from './xrp-currency-amount' 4 | 5 | /* 6 | * Represents a CheckCash transaction on the XRP Ledger. 7 | * 8 | * A CheckCash transaction attempts to redeem a Check object in the ledger to receive up to the amount 9 | * authorized by the corresponding CheckCreate transaction. 10 | * 11 | * @see: https://xrpl.org/checkcash.html 12 | */ 13 | export default class XrpCheckCash { 14 | /** 15 | * Constructs an XrpCheckCash from a CheckCash protocol buffer. 16 | * 17 | * @param checkCash a CheckCash (protobuf object) whose field values will be used to construct an XrpCheckCash 18 | * @return an XrpCheckCash with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L132 20 | */ 21 | public static from(checkCash: CheckCash): XrpCheckCash { 22 | const checkId = checkCash.getCheckId()?.getValue_asB64() 23 | if (!checkId) { 24 | throw new XrpError( 25 | XrpErrorType.MalformedProtobuf, 26 | 'CheckCash protobuf is missing `checkID` field.', 27 | ) 28 | } 29 | 30 | const amountCurrencyAmount = checkCash.getAmount()?.getValue() 31 | const amount = amountCurrencyAmount 32 | ? XrpCurrencyAmount.from(amountCurrencyAmount) 33 | : undefined 34 | 35 | const deliverMinCurrencyAmount = checkCash.getDeliverMin()?.getValue() 36 | const deliverMin = deliverMinCurrencyAmount 37 | ? XrpCurrencyAmount.from(deliverMinCurrencyAmount) 38 | : undefined 39 | 40 | if (!deliverMin && !amount) { 41 | throw new XrpError( 42 | XrpErrorType.MalformedProtobuf, 43 | 'CheckCash protobuf contains neither `amount` nor `deliverMin` fields.', 44 | ) 45 | } 46 | 47 | return new XrpCheckCash(checkId, amount, deliverMin) 48 | } 49 | 50 | /** 51 | * @param checkId The ID of the Check ledger object to cash, as a 64-character hexadecimal string. 52 | * @param amount (Optional) Redeem the Check for exactly this amount, if possible. 53 | * The currency must match that of the SendMax of the corresponding CheckCreate transaction. 54 | * You must provide either this field or deliverMin. 55 | * @param deliverMin (Optional) Redeem the Check for at least this amount and for as much as possible. 56 | * The currency must match that of the SendMax of the corresponding CheckCreate transaction. 57 | * You must provide either this field or amount. 58 | */ 59 | private constructor( 60 | readonly checkId: string, 61 | readonly amount?: XrpCurrencyAmount, 62 | readonly deliverMin?: XrpCurrencyAmount, 63 | ) {} 64 | } 65 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-check-create.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { CheckCreate } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | import XrpCurrencyAmount from './xrp-currency-amount' 6 | 7 | /* 8 | * Represents a CheckCreate transaction on the XRP Ledger. 9 | * 10 | * A CheckCreate transaction creates a Check object in the ledger, which is a deferred payment that can be cashed 11 | * by its intended destination. The sender of this transaction is the sender of the Check. 12 | * 13 | * @see: https://xrpl.org/checkcreate.html 14 | */ 15 | export default class XrpCheckCreate { 16 | /** 17 | * Constructs an XrpCheckCreate from a CheckCreate protocol buffer. 18 | * 19 | * @param checkCreate a CheckCreate (protobuf object) whose field values will be used to construct an XrpCheckCreate 20 | * @return an XrpCheckCreate with its fields set via the analogous protobuf fields. 21 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L145 22 | */ 23 | public static from( 24 | checkCreate: CheckCreate, 25 | xrplNetwork: XrplNetwork, 26 | ): XrpCheckCreate { 27 | const destination = checkCreate.getDestination()?.getValue()?.getAddress() 28 | if (!destination) { 29 | throw new XrpError( 30 | XrpErrorType.MalformedProtobuf, 31 | 'CheckCreate protobuf is missing `destination` field.', 32 | ) 33 | } 34 | const destinationTag = checkCreate.getDestinationTag()?.getValue() 35 | 36 | const destinationXAddress = XrpUtils.encodeXAddress( 37 | destination, 38 | destinationTag, 39 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 40 | ) 41 | if (!destinationXAddress) { 42 | throw new XrpError( 43 | XrpErrorType.MalformedProtobuf, 44 | 'Cannot construct XAddress from CheckCreate protobuf `destination` and `destinationTag` fields.', 45 | ) 46 | } 47 | 48 | const sendMaxCurrencyAmount = checkCreate.getSendMax()?.getValue() 49 | if (sendMaxCurrencyAmount == undefined) { 50 | throw new XrpError( 51 | XrpErrorType.MalformedProtobuf, 52 | 'CheckCreate protobuf is missing `SendMax` field.', 53 | ) 54 | } 55 | const sendMax = XrpCurrencyAmount.from(sendMaxCurrencyAmount) 56 | 57 | const expiration = checkCreate.getExpiration()?.getValue() 58 | const invoiceId = checkCreate.getInvoiceId()?.getValue_asB64() 59 | return new XrpCheckCreate( 60 | destinationXAddress, 61 | sendMax, 62 | expiration, 63 | invoiceId, 64 | ) 65 | } 66 | 67 | /** 68 | * @param destinationXAddress The unique address and (optional) destination tag of the account that can cash the Check, 69 | * encoded as an X-address (see https://xrpaddress.info/). 70 | * @param sendMax Maximum amount of source currency the Check is allowed to debit the sender, including transfer fees on non-XRP currencies. 71 | * The Check can only credit the destination with the same currency (from the same issuer, for non-XRP currencies). 72 | * For non-XRP amounts, the nested field names MUST be lower-case. 73 | * @param expiration (Optional) Time after which the Check is no longer valid, in seconds since the Ripple Epoch. 74 | * @param invoiceId (Optional) Arbitrary 256-bit hash representing a specific reason or identifier for this Check. 75 | */ 76 | private constructor( 77 | readonly destinationXAddress: string, 78 | readonly sendMax: XrpCurrencyAmount, 79 | readonly expiration?: number, 80 | readonly invoiceId?: string, 81 | ) {} 82 | } 83 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-currency-amount.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { CurrencyAmount } from '../Generated/web/org/xrpl/rpc/v1/amount_pb' 3 | import XrplIssuedCurrency from './xrpl-issued-currency' 4 | 5 | /** 6 | * An amount of currency on the XRP Ledger 7 | * @see: https://xrpl.org/basic-data-types.html#specifying-currency-amounts 8 | */ 9 | export default class XrpCurrencyAmount { 10 | /** 11 | * Constructs an XrpCurrencyAmount from a CurrencyAmount. 12 | * 13 | * @param currencyAmount a CurrencyAmount (protobuf object) whose field values will be used 14 | * to construct an XrpCurrencyAmount 15 | * @returns an XrpCurrencyAmount with its fields set via the analogous protobuf fields. 16 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/amount.proto#L10 17 | */ 18 | public static from(currencyAmount: CurrencyAmount): XrpCurrencyAmount { 19 | // Mutually exclusive: either drops or issuedCurrency is set in an XRPCurrencyAmount 20 | const issuedCurrencyAmount = currencyAmount.getIssuedCurrencyAmount() 21 | const xrpAmount = currencyAmount.getXrpAmount() 22 | if (issuedCurrencyAmount !== undefined && xrpAmount === undefined) { 23 | const issuedCurrency = XrplIssuedCurrency.from(issuedCurrencyAmount) 24 | return new XrpCurrencyAmount(undefined, issuedCurrency) 25 | } 26 | if (xrpAmount !== undefined && issuedCurrencyAmount === undefined) { 27 | const drops = xrpAmount.getDrops() 28 | return new XrpCurrencyAmount(drops, undefined) 29 | } 30 | 31 | throw new XrpError( 32 | XrpErrorType.MalformedProtobuf, 33 | 'CurrencyAmount protobuf does not have an amount set.', 34 | ) 35 | } 36 | 37 | /** 38 | * Note: Mutually exclusive fields - only drops XOR issuedCurrency should be set. 39 | * 40 | * @param drops An amount of XRP, specified in drops. 41 | * @param issuedCurrency An amount of an issued currency. 42 | */ 43 | private constructor( 44 | readonly drops?: string, 45 | readonly issuedCurrency?: XrplIssuedCurrency, 46 | ) {} 47 | } 48 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-currency.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { Currency } from '../Generated/web/org/xrpl/rpc/v1/amount_pb' 3 | 4 | /** 5 | * An issued currency on the XRP Ledger 6 | * @see: https://xrpl.org/currency-formats.html#currency-codes 7 | */ 8 | export default class XrpCurrency { 9 | /** 10 | * Constructs an XrpCurrency from a Currency. 11 | * 12 | * @param currency a Currency (protobuf object) whose field values will be used 13 | * to construct an XRPCurrency 14 | * @returns an XrpCurrency with its fields set via the analogous protobuf fields. 15 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/amount.proto#L41 16 | */ 17 | public static from(currency: Currency): XrpCurrency { 18 | const currencyName = currency.getName() 19 | const currencyCode = currency.getCode_asB64() 20 | if (!currencyName) { 21 | throw new XrpError( 22 | XrpErrorType.MalformedProtobuf, 23 | 'Currency protobuf missing required field `name`.', 24 | ) 25 | } 26 | if (!currencyCode) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'Currency protobuf missing required field `code`.', 30 | ) 31 | } 32 | return new XrpCurrency(currencyName, currencyCode) 33 | } 34 | 35 | /** 36 | * @param name 3 character ASCII code 37 | * @param code 160 bit currency code. 20 bytes 38 | */ 39 | private constructor(readonly name: string, readonly code: string) {} 40 | } 41 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-deposit-preauth.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { DepositPreauth } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | 6 | /* 7 | * Represents a DepositPreauth transaction on the XRP Ledger. 8 | * 9 | * A DepositPreauth transaction gives another account pre-approval to deliver payments to the sender of this transaction. 10 | * This is only useful if the sender of this transaction is using (or plans to use) Deposit Authorization. 11 | * 12 | * @see: https://xrpl.org/depositpreauth.html 13 | */ 14 | export default class XrpDepositPreauth { 15 | /** 16 | * Constructs an XrpDepositPreauth from a DepositPreauth protocol buffer. 17 | * 18 | * @param depositPreauth a DepositPreauth (protobuf object) whose field values will be used to construct an XrpDepositPreauth 19 | * @return an XrpDepositPreauth with its fields set via the analogous protobuf fields. 20 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L159 21 | */ 22 | public static from( 23 | depositPreauth: DepositPreauth, 24 | xrplNetwork: XrplNetwork, 25 | ): XrpDepositPreauth { 26 | const authorize = depositPreauth.getAuthorize()?.getValue()?.getAddress() 27 | const unauthorize = depositPreauth 28 | .getUnauthorize() 29 | ?.getValue() 30 | ?.getAddress() 31 | 32 | if (authorize) { 33 | const authorizeXAddress = XrpUtils.encodeXAddress( 34 | authorize, 35 | undefined, 36 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 37 | ) 38 | if (!authorizeXAddress) { 39 | throw new XrpError( 40 | XrpErrorType.MalformedProtobuf, 41 | 'Cannot construct XAddress from DepositPreauth protobuf `authorize` field.', 42 | ) 43 | } 44 | return new XrpDepositPreauth(authorizeXAddress, undefined) 45 | } 46 | 47 | if (unauthorize) { 48 | const unauthorizeXAddress = XrpUtils.encodeXAddress( 49 | unauthorize, 50 | undefined, 51 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 52 | ) 53 | if (!unauthorizeXAddress) { 54 | throw new XrpError( 55 | XrpErrorType.MalformedProtobuf, 56 | 'Cannot construct XAddress from DepositPreauth protobuf `unauthorize` field.', 57 | ) 58 | } 59 | return new XrpDepositPreauth(undefined, unauthorizeXAddress) 60 | } 61 | 62 | throw new XrpError( 63 | XrpErrorType.MalformedProtobuf, 64 | 'DepositPreauth protobuf provides neither `Authorize` nor `Unauthorize` fields.', 65 | ) 66 | } 67 | 68 | /** 69 | * Note: authorize and unauthorize are mutually exclusive fields: one but not both should be set. 70 | * Addresses are encoded as X-addresses. See https://xrpaddress.info/. 71 | * @param authorizeXAddress (Optional) The XRP Ledger address of the sender to preauthorize, encoded as an X-address. 72 | * @param unauthorizeXAddress (Optional) The XRP Ledger address of a sender whose preauthorization should be revoked, 73 | * encoded as an X-address. 74 | */ 75 | private constructor( 76 | readonly authorizeXAddress?: string, 77 | readonly unauthorizeXAddress?: string, 78 | ) {} 79 | } 80 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-escrow-cancel.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { EscrowCancel } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | 6 | /* 7 | * Represents an EscrowCancel transaction on the XRP Ledger. 8 | * 9 | * An EscrowCancel transaction returns escrowed XRP to the sender. 10 | * 11 | * @see: https://xrpl.org/escrowcancel.html 12 | */ 13 | export default class XrpEscrowCancel { 14 | /** 15 | * Constructs an XrpEscrowCancel from an EscrowCancel protocol buffer. 16 | * 17 | * @param escrowCancel an EscrowCancel (protobuf object) whose field values will be used to construct an XrpEscrowCancel 18 | * @return an XrpEscrowCancel with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L170 20 | */ 21 | public static from( 22 | escrowCancel: EscrowCancel, 23 | xrplNetwork: XrplNetwork, 24 | ): XrpEscrowCancel { 25 | const owner = escrowCancel.getOwner()?.getValue()?.getAddress() 26 | if (!owner) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'EscrowCancel protobuf is missing valid `owner` field.', 30 | ) 31 | } 32 | const ownerXAddress = XrpUtils.encodeXAddress( 33 | owner, 34 | undefined, 35 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 36 | ) 37 | 38 | // ownerXAddress and offerSequence are both required fields. 39 | if (!ownerXAddress) { 40 | throw new XrpError( 41 | XrpErrorType.MalformedProtobuf, 42 | 'Cannot construct XAddress from EscrowCancel protobuf `owner` field.', 43 | ) 44 | } 45 | const offerSequence = escrowCancel.getOfferSequence()?.getValue() 46 | if (!offerSequence) { 47 | throw new XrpError( 48 | XrpErrorType.MalformedProtobuf, 49 | 'EscrowCancel protobuf is missing valid `offerSequence` field.', 50 | ) 51 | } 52 | return new XrpEscrowCancel(ownerXAddress, offerSequence) 53 | } 54 | 55 | /** 56 | * @param ownerXAddress Address of the source account that funded the escrow payment, encoded as an X-address (see https://xrpaddress.info/). 57 | * @param offerSequence Transaction sequence of EscrowCreate transaction that created the escrow to cancel. 58 | */ 59 | private constructor( 60 | readonly ownerXAddress: string, 61 | readonly offerSequence: number, 62 | ) {} 63 | } 64 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-escrow-finish.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { EscrowFinish } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | 6 | /* 7 | * Represents an EscrowFinish transaction on the XRP Ledger. 8 | * 9 | * An EscrowFinish transaction delivers XRP from a held payment to the recipient. 10 | * 11 | * @see: https://xrpl.org/escrowfinish.html 12 | */ 13 | export default class XrpEscrowFinish { 14 | /** 15 | * Constructs an XrpEscrowFinish from an EscrowFinish protocol buffer. 16 | * 17 | * @param escrowFinish an EscrowFinish (protobuf object) whose field values will be used to construct an XrpEscrowFinish 18 | * @return an XrpEscrowFinish with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L194 20 | */ 21 | public static from( 22 | escrowFinish: EscrowFinish, 23 | xrplNetwork: XrplNetwork, 24 | ): XrpEscrowFinish { 25 | const owner = escrowFinish.getOwner()?.getValue()?.getAddress() 26 | if (!owner) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'EscrowFinish protobuf is missing required `owner` field.', 30 | ) 31 | } 32 | const ownerXAddress = XrpUtils.encodeXAddress( 33 | owner, 34 | undefined, 35 | xrplNetwork === XrplNetwork.Test || xrplNetwork === XrplNetwork.Dev, 36 | ) 37 | if (!ownerXAddress) { 38 | throw new XrpError( 39 | XrpErrorType.MalformedProtobuf, 40 | 'Cannot construct XAddress from EscrowFinish protobuf `owner` field.', 41 | ) 42 | } 43 | 44 | const offerSequence = escrowFinish.getOfferSequence()?.getValue() 45 | if (offerSequence === undefined) { 46 | throw new XrpError( 47 | XrpErrorType.MalformedProtobuf, 48 | 'EscrowFinish protobuf is missing required `offerSequence` field.', 49 | ) 50 | } 51 | 52 | const condition = escrowFinish.getCondition()?.getValue_asB64() 53 | const fulfillment = escrowFinish.getFulfillment()?.getValue_asB64() 54 | 55 | return new XrpEscrowFinish( 56 | ownerXAddress, 57 | offerSequence, 58 | condition, 59 | fulfillment, 60 | ) 61 | } 62 | 63 | /** 64 | * @param ownerXAddress Address of the source account that funded the held payment, encoded as an X-address (see https://xrpaddress.info/). 65 | * @param offerSequence Transaction sequence of EscrowCreate transaction that created the held payment to finish. 66 | * @param condition (Optional) Hex value matching the previously-supplied PREIMAGE-SHA-256 crypto-condition of the held payment. 67 | * @param fulfillment (Optional) Hex value of the PREIMAGE-SHA-256 crypto-condition fulfillment matching the held payment's Condition. 68 | */ 69 | private constructor( 70 | readonly ownerXAddress: string, 71 | readonly offerSequence: number, 72 | readonly condition?: string, 73 | readonly fulfillment?: string, 74 | ) {} 75 | } 76 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-memo.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { Memo } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import { stringToUint8Array } from '../../Common/utils' 4 | 5 | /** 6 | * Provides a means of passing a string to a memo that allows for user 7 | * specification as to whether or not the string is already a hex string. 8 | */ 9 | export interface MemoField { 10 | value?: string 11 | isHex?: boolean 12 | } 13 | 14 | /* 15 | * Represents a memo on the XRPLedger. 16 | * @see: https://xrpl.org/transaction-common-fields.html#memos-field 17 | */ 18 | export default class XrpMemo { 19 | /** 20 | * Constructs an XrpMemo from a Memo. 21 | * 22 | * @param memo a Memo (protobuf object) whose field values will be used 23 | * to construct an XrpMemo 24 | * @return an XrpMemo with its fields set via the analogous protobuf fields. 25 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L80 26 | */ 27 | public static from(memo: Memo): XrpMemo { 28 | const data = memo.getMemoData()?.getValue_asU8() 29 | const format = memo.getMemoFormat()?.getValue_asU8() 30 | const type = memo.getMemoType()?.getValue_asU8() 31 | if (data === undefined && format === undefined && type === undefined) { 32 | throw new XrpError( 33 | XrpErrorType.MalformedProtobuf, 34 | 'Memo protobuf missing all fields (requires at least one field).', 35 | ) 36 | } 37 | return new XrpMemo(data, format, type) 38 | } 39 | 40 | /** 41 | * Converts strings that may or may not be hex (as indicated by the MemoField argument) into the 42 | * Uint8Array fields needed for an XrpMemo instance. 43 | * 44 | * @param data - a MemoField with an optional string which may or may not be converted to a hex string. 45 | * @param format - a MemoField with an optional string which may or may not be converted to a hex string. 46 | * @param type - a MemoField with an optional string which may or may not be converted to a hex string. 47 | * @returns an XrpMemo with each potentially hex-encoded field set to the Uint8Array version of said field. 48 | */ 49 | public static fromMemoFields( 50 | data?: MemoField, 51 | format?: MemoField, 52 | type?: MemoField, 53 | ): XrpMemo { 54 | const memoData = data 55 | ? stringToUint8Array(data.value, data.isHex) 56 | : stringToUint8Array('', false) 57 | const memoFormat = format 58 | ? stringToUint8Array(format.value, format.isHex) 59 | : stringToUint8Array('', false) 60 | const memoType = type 61 | ? stringToUint8Array(type.value, type.isHex) 62 | : stringToUint8Array('', false) 63 | return { 64 | data: memoData, 65 | format: memoFormat, 66 | type: memoType, 67 | } 68 | } 69 | 70 | /** 71 | * @param data Arbitrary hex value, conventionally containing the content of the memo. 72 | * @param format Hex value representing characters allowed in URLs. Conventionally containing 73 | * information on how the memo is encoded, for example as a MIME type. 74 | * @param type Hex value representing characters allowed in URLs. Conventionally, a unique 75 | * relation (according to RFC 5988) that defines the format of this memo. 76 | */ 77 | private constructor( 78 | readonly data?: Uint8Array, 79 | readonly format?: Uint8Array, 80 | readonly type?: Uint8Array, 81 | ) {} 82 | } 83 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-offer-cancel.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { OfferCancel } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | 4 | /* 5 | * Represents an OfferCancel transaction on the XRP Ledger. 6 | * 7 | * An OfferCancel transaction removes an Offer object from the XRP Ledger. 8 | * 9 | * @see: https://xrpl.org/offercancel.html 10 | */ 11 | export default class XrpOfferCancel { 12 | /** 13 | * Constructs an XrpOfferCancel from an OfferCancel protocol buffer. 14 | * 15 | * @param offerCancel an OfferCancel (protobuf object) whose field values will be used to construct an XrpOfferCancel 16 | * @return an XrpOfferCancel with its fields set via the analogous protobuf fields. 17 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L206 18 | */ 19 | public static from(offerCancel: OfferCancel): XrpOfferCancel { 20 | const offerSequence = offerCancel.getOfferSequence()?.getValue() 21 | if (!offerSequence) { 22 | throw new XrpError( 23 | XrpErrorType.MalformedProtobuf, 24 | 'OfferCancel protobuf is missing `offerSequence` field.', 25 | ) 26 | } 27 | return new XrpOfferCancel(offerSequence) 28 | } 29 | 30 | /** 31 | * @param offerSequence The sequence number of a previous OfferCreate transaction. If specified, 32 | * cancel any offer object in the ledger that was created by that transaction. 33 | * It is not considered an error if the offer specified does not exist. 34 | */ 35 | private constructor(readonly offerSequence: number) {} 36 | } 37 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-offer-create.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { OfferCreate } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrencyAmount from './xrp-currency-amount' 4 | 5 | /* 6 | * Represents an OfferCreate transaction on the XRP Ledger. 7 | * 8 | * An OfferCreate transaction is effectively a limit order. 9 | * It defines an intent to exchange currencies, and creates 10 | * an Offer object if not completely fulfilled when placed. 11 | * Offers can be partially fulfilled. 12 | * 13 | * @see: https://xrpl.org/offercreate.html 14 | */ 15 | export default class XrpOfferCreate { 16 | /** 17 | * Constructs an XrpOfferCreate from an OfferCreate protocol buffer. 18 | * 19 | * @param offerCreate an OfferCreate (protobuf object) whose field values will be used to construct an XrpOfferCreate 20 | * @return an XrpOfferCreate with its fields set via the analogous protobuf fields. 21 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L212 22 | */ 23 | public static from(offerCreate: OfferCreate): XrpOfferCreate { 24 | // takerGets and takerPays are required fields 25 | const takerGetsCurrencyAmount = offerCreate.getTakerGets()?.getValue() 26 | if (!takerGetsCurrencyAmount) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'OfferCreate protobuf is missing `takerGets` field.', 30 | ) 31 | } 32 | const takerGets = XrpCurrencyAmount.from(takerGetsCurrencyAmount) 33 | 34 | const takerPaysCurrencyAmount = offerCreate.getTakerPays()?.getValue() 35 | if (!takerPaysCurrencyAmount) { 36 | throw new XrpError( 37 | XrpErrorType.MalformedProtobuf, 38 | 'OfferCreate protobuf is missing `takerPays` field.', 39 | ) 40 | } 41 | const takerPays = XrpCurrencyAmount.from(takerPaysCurrencyAmount) 42 | const expiration = offerCreate.getExpiration()?.getValue() 43 | const offerSequence = offerCreate.getOfferSequence()?.getValue() 44 | 45 | return new XrpOfferCreate(takerGets, takerPays, expiration, offerSequence) 46 | } 47 | 48 | /** 49 | * @param takerGets The amount and type of currency being provided by the offer creator. 50 | * @param takerPays The amount and type of currency being requested by the offer creator. 51 | * @param expiration (Optional) Time after which the offer is no longer active, in seconds since the Ripple Epoch. 52 | * @param offerSequence (Optional) An offer to delete first, specified in the same way as OfferCancel. 53 | */ 54 | private constructor( 55 | readonly takerGets: XrpCurrencyAmount, 56 | readonly takerPays: XrpCurrencyAmount, 57 | readonly expiration?: number, 58 | readonly offerSequence?: number, 59 | ) {} 60 | } 61 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-path-element.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { Payment } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrency from './xrp-currency' 4 | 5 | type PathElement = Payment.PathElement 6 | 7 | /* 8 | * A path step in an XRP Ledger Path. 9 | * @see: https://xrpl.org/paths.html#path-steps 10 | */ 11 | export default class XrpPathElement { 12 | /** 13 | * Constructs an XrpPathElement from a PathElement. 14 | * 15 | * @param pathElement a PathElement (protobuf object) whose field values will be used 16 | * to construct an XrpPathElement 17 | * @returns an XrpPathElement with its fields set via the analogous protobuf fields. 18 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L227 19 | */ 20 | public static from(pathElement: PathElement): XrpPathElement { 21 | const account = pathElement.getAccount()?.getAddress() 22 | const currency = pathElement.getCurrency() 23 | if (account && currency) { 24 | throw new XrpError( 25 | XrpErrorType.MalformedProtobuf, 26 | 'PathElement protobuf should not contain both `account` and `currency` fields.', 27 | ) 28 | } 29 | const issuer = pathElement.getIssuer()?.getAddress() 30 | if (account && issuer) { 31 | throw new XrpError( 32 | XrpErrorType.MalformedProtobuf, 33 | 'PathElement protobuf should not contain both `account` and `issuer` fields.', 34 | ) 35 | } 36 | // TODO check that `issuer` is omitted if the `currency` is XRP 37 | const xrpCurrency = currency && XrpCurrency.from(currency) 38 | if (!account && !xrpCurrency && !issuer) { 39 | throw new XrpError( 40 | XrpErrorType.MalformedProtobuf, 41 | 'PathElement protobuf is missing all fields.', 42 | ) 43 | } 44 | return new XrpPathElement(account, xrpCurrency, issuer) 45 | } 46 | 47 | /** 48 | * 49 | * @param account (Optional) If present, this path step represents rippling through the specified address. 50 | * MUST NOT be provided if this path element specifies the currency or issuer fields. 51 | * @param currency (Optional) If present, this path element represents changing currencies through an order book. 52 | * The currency specified indicates the new currency. MUST NOT be provided if this path 53 | * element specifies the account field. 54 | * @param issuer (Optional) If present, this path element represents changing currencies and this address 55 | * defines the issuer of the new currency. If omitted in a path element with a non-XRP currency, 56 | * a previous element of the path defines the issuer. If present when currency is omitted, 57 | * indicates a path element that uses an order book between same-named currencies with different issuers. 58 | * MUST be omitted if the currency is XRP. MUST NOT be provided if this element specifies the account field. 59 | */ 60 | private constructor( 61 | readonly account?: string, 62 | readonly currency?: XrpCurrency, 63 | readonly issuer?: string, 64 | ) {} 65 | } 66 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-path.ts: -------------------------------------------------------------------------------- 1 | import { Payment } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 2 | import XrpPathElement from './xrp-path-element' 3 | 4 | type Path = Payment.Path 5 | 6 | /* 7 | * A path in the XRP Ledger. 8 | * @see: https://xrpl.org/paths.html 9 | */ 10 | export default class XrpPath { 11 | /** 12 | * Constructs an XrpPath from a Path. 13 | * 14 | * @param path a Path (protobuf object) whose field values will be used 15 | * to construct an XrpPath 16 | * @returns an XrpPath with its fields set via the analogous protobuf fields. 17 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L237 18 | */ 19 | public static from(path: Path): XrpPath { 20 | const pathElements = path 21 | .getElementsList() 22 | .map((pathElement) => XrpPathElement.from(pathElement)) 23 | return new XrpPath(pathElements) 24 | } 25 | 26 | /** 27 | * 28 | * @param pathElements List of XrpPathElements that make up this XRPPath. 29 | */ 30 | private constructor(readonly pathElements: Array) {} 31 | } 32 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-payment-channel-claim.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { PaymentChannelClaim } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrencyAmount from './xrp-currency-amount' 4 | 5 | /* 6 | * Represents a PaymentChannelClaim transaction on the XRP Ledger. 7 | * 8 | * A PaymentChannelClaim transaction claims XRP from a payment channel, adjusts the payment channel's expiration, or both. 9 | * 10 | * @see: https://xrpl.org/paymentchannelclaim.html 11 | */ 12 | export default class XrpPaymentChannelClaim { 13 | /** 14 | * Constructs an XrpPaymentChannelClaim from a PaymentChannelClaim protocol buffer. 15 | * 16 | * @param paymentChannelClaim a PaymentChannelClaim (protobuf object) whose field values will be used to construct an XrpPaymentChannelClaim 17 | * @return an XrpPaymentChannelClaim with its fields set via the analogous protobuf fields. 18 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L258 19 | */ 20 | public static from( 21 | paymentChannelClaim: PaymentChannelClaim, 22 | ): XrpPaymentChannelClaim { 23 | const channel = paymentChannelClaim.getChannel()?.getValue_asB64() 24 | if (!channel) { 25 | throw new XrpError( 26 | XrpErrorType.MalformedProtobuf, 27 | 'PaymentChannelClaim protobuf does not contain `channel` field.', 28 | ) 29 | } 30 | 31 | const balanceCurrencyAmount = paymentChannelClaim.getBalance()?.getValue() 32 | let balance 33 | if (balanceCurrencyAmount) { 34 | balance = XrpCurrencyAmount.from(balanceCurrencyAmount) 35 | } 36 | 37 | const amountCurrencyAmount = paymentChannelClaim.getAmount()?.getValue() 38 | let amount 39 | if (amountCurrencyAmount) { 40 | amount = XrpCurrencyAmount.from(amountCurrencyAmount) 41 | } 42 | 43 | const signature = paymentChannelClaim 44 | .getPaymentChannelSignature() 45 | ?.getValue_asB64() 46 | 47 | const publicKey = paymentChannelClaim.getPublicKey()?.getValue_asB64() 48 | if (signature && !publicKey) { 49 | throw new XrpError( 50 | XrpErrorType.MalformedProtobuf, 51 | 'PaymentChannelClaim protobuf does not contain `publicKey` field when `signature` field is provided.', 52 | ) 53 | } 54 | 55 | return new XrpPaymentChannelClaim( 56 | channel, 57 | balance, 58 | amount, 59 | signature, 60 | publicKey, 61 | ) 62 | } 63 | 64 | /** 65 | * @param channel The unique ID of the channel, as a 64-character hexadecimal string. 66 | * @param balance (Optional) Total amount of XRP, in drops, delivered by this channel after processing this claim. 67 | * Required to deliver XRP. Must be more than the total amount delivered by the channel so far, 68 | * but not greater than the Amount of the signed claim. Must be provided except when closing the channel. 69 | * @param amount (Optional) The amount of XRP, in drops, authorized by the Signature. 70 | * This must match the amount in the signed message. 71 | * This is the cumulative amount of XRP that can be dispensed by the channel, including XRP previously redeemed. 72 | * @param signature (Optional) The signature of this claim, as hexadecimal. The signed message contains the channel ID and the 73 | * amount of the claim. Required unless the sender of the transaction is the source address of the channel. 74 | * @param publicKey (Optional) The public key used for the signature, as hexadecimal. This must match the PublicKey stored 75 | * in the ledger for the channel. Required unless the sender of the transaction is the source address of 76 | * the channel and the Signature field is omitted. (The transaction includes the PubKey so that rippled 77 | * can check the validity of the signature before trying to apply the transaction to the ledger.) 78 | */ 79 | private constructor( 80 | readonly channel: string, 81 | readonly balance?: XrpCurrencyAmount, 82 | readonly amount?: XrpCurrencyAmount, 83 | readonly signature?: string, 84 | readonly publicKey?: string, 85 | ) {} 86 | } 87 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-payment-channel-fund.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { PaymentChannelFund } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrencyAmount from './xrp-currency-amount' 4 | 5 | /* 6 | * Represents a PaymentChannelFund transaction on the XRP Ledger. 7 | * 8 | * A PaymentChannelFund transaction adds additional XRP to an open payment channel, updates the expiration 9 | * time of the channel, or both. Only the source address of the channel can use this transaction. 10 | * (Transactions from other addresses fail with the error tecNO_PERMISSION.) 11 | * 12 | * @see: https://xrpl.org/paymentchannelfund.html 13 | */ 14 | export default class XrpPaymentChannelFund { 15 | /** 16 | * Constructs an XrpPaymentChannelFund from a PaymentChannelFund protocol buffer. 17 | * 18 | * @param paymentChannelFund a PaymentChannelFund (protobuf object) whose field values will be used to construct an XrpPaymentChannelFund 19 | * @return an XrpPaymentChannelFund with its fields set via the analogous protobuf fields. 20 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L288 21 | */ 22 | public static from( 23 | paymentChannelFund: PaymentChannelFund, 24 | ): XrpPaymentChannelFund { 25 | const channel = paymentChannelFund.getChannel()?.getValue_asB64() 26 | if (!channel) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'PaymentChannelFund protobuf does not contain `channel` field.', 30 | ) 31 | } 32 | 33 | const amountCurrencyAmount = paymentChannelFund.getAmount()?.getValue() 34 | if (amountCurrencyAmount === undefined) { 35 | throw new XrpError( 36 | XrpErrorType.MalformedProtobuf, 37 | 'PaymentChannelFund protobuf does not contain `amount` field.', 38 | ) 39 | } 40 | const amount = XrpCurrencyAmount.from(amountCurrencyAmount) 41 | 42 | const expiration = paymentChannelFund.getExpiration()?.getValue() 43 | 44 | return new XrpPaymentChannelFund(channel, amount, expiration) 45 | } 46 | 47 | /** 48 | * 49 | * @param channel The unique ID of the channel to fund, as a 64-character hexadecimal string. 50 | * @param amount Amount of XRP, in drops to add to the channel. To set the expiration for a channel without 51 | * adding more XRP, set this to "0". 52 | * @param expiration (Optional) New Expiration time to set for the channel, in seconds since the Ripple Epoch. 53 | * This must be later than either the current time plus the SettleDelay of the channel, 54 | * or the existing Expiration of the channel. After the Expiration time, any transaction 55 | * that would access the channel closes the channel without taking its normal action. 56 | * Any unspent XRP is returned to the source address when the channel closes. 57 | * (Expiration is separate from the channel's immutable CancelAfter time.) 58 | * For more information, see the PayChannel ledger object type: https://xrpl.org/paychannel.html 59 | */ 60 | private constructor( 61 | readonly channel: string, 62 | readonly amount: XrpCurrencyAmount, 63 | readonly expiration?: number, 64 | ) {} 65 | } 66 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-payment.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { Payment } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | import XrpCurrencyAmount from './xrp-currency-amount' 6 | import XrpPath from './xrp-path' 7 | 8 | /** 9 | * Represents a payment on the XRP Ledger. 10 | * @see: https://xrpl.org/payment.html 11 | */ 12 | export default class XrpPayment { 13 | /** 14 | * Constructs an XrpPayment from a Payment. 15 | * 16 | * @param payment a Payment (protobuf object) whose field values will be used 17 | * to construct an XrpPayment 18 | * @param xrplNetwork The Xrpl network from which this object was retrieved. 19 | * @return an XrpPayment with its fields set via the analogous protobuf fields. 20 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L224 21 | */ 22 | public static from(payment: Payment, xrplNetwork: XrplNetwork): XrpPayment { 23 | const paymentAmountValue = payment.getAmount()?.getValue() 24 | if (paymentAmountValue === undefined) { 25 | throw new XrpError( 26 | XrpErrorType.MalformedProtobuf, 27 | 'Payment protobuf is missing required `amount` field.', 28 | ) 29 | } 30 | const amount = XrpCurrencyAmount.from(paymentAmountValue) 31 | // For non-XRP amounts, the nested field names in `amount` are lower-case. 32 | 33 | const destination = payment.getDestination()?.getValue()?.getAddress() 34 | if (destination === undefined) { 35 | throw new XrpError( 36 | XrpErrorType.MalformedProtobuf, 37 | 'Payment protobuf is missing required `destination` field.', 38 | ) 39 | } 40 | 41 | const destinationTag = payment.getDestinationTag()?.getValue() 42 | 43 | const destinationXAddress = XrpUtils.encodeXAddress( 44 | destination, 45 | destinationTag, 46 | xrplNetwork === XrplNetwork.Test, 47 | ) 48 | if (!destinationXAddress) { 49 | throw new XrpError( 50 | XrpErrorType.MalformedProtobuf, 51 | 'Payment protobuf destination cannot be encoded into an X-address.', 52 | ) 53 | } 54 | 55 | const invoiceID = payment.getInvoiceId()?.getValue_asU8() 56 | 57 | const paths = 58 | payment.getPathsList()?.length > 0 59 | ? payment.getPathsList().map((path) => XrpPath.from(path)) 60 | : undefined 61 | if (paths !== undefined && amount.drops !== undefined) { 62 | throw new XrpError( 63 | XrpErrorType.MalformedProtobuf, 64 | 'Payment protobuf `paths` field should be omitted for XRP-to-XRP interactions.', 65 | ) 66 | } 67 | 68 | const paymentSendMaxValue = payment.getSendMax()?.getValue() 69 | const sendMax = 70 | paymentSendMaxValue && XrpCurrencyAmount.from(paymentSendMaxValue) 71 | if (sendMax !== undefined && amount.drops !== undefined) { 72 | throw new XrpError( 73 | XrpErrorType.MalformedProtobuf, 74 | 'Payment protobuf `sendMax` field should be omitted for XRP-to-XRP interactions.', 75 | ) 76 | } 77 | 78 | const paymentDeliverMinValue = payment.getDeliverMin()?.getValue() 79 | const deliverMin = 80 | paymentDeliverMinValue && XrpCurrencyAmount.from(paymentDeliverMinValue) 81 | // For non-XRP amounts, the nested field names in `deliverMin` are lower-case. 82 | 83 | return new XrpPayment( 84 | amount, 85 | destinationXAddress, 86 | deliverMin, 87 | invoiceID, 88 | paths, 89 | sendMax, 90 | ) 91 | } 92 | 93 | /** 94 | * @param amount The amount of currency to deliver. 95 | * @param destinationXAddress The address and (optional) destination tag of the account receiving the payment, encoded in X-address format. 96 | * See https://xrpaddress.info/. 97 | * @param deliverMin (Optional) Minimum amount of destination currency this transaction should deliver. 98 | * @param invoiceID (Optional) Arbitrary 256-bit hash representing a specific reason or identifier for this payment. 99 | * @param paths Array of payment paths to be used for this transaction. Must be omitted for XRP-to-XRP transactions. 100 | * @param sendMax (Optional) Highest amount of source currency this transaction is allowed to cost. 101 | */ 102 | private constructor( 103 | readonly amount: XrpCurrencyAmount, 104 | readonly destinationXAddress: string, 105 | readonly deliverMin?: XrpCurrencyAmount, 106 | readonly invoiceID?: Uint8Array, 107 | readonly paths?: Array, 108 | readonly sendMax?: XrpCurrencyAmount, 109 | ) {} 110 | } 111 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-set-regular-key.ts: -------------------------------------------------------------------------------- 1 | import { SetRegularKey } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 2 | 3 | /* 4 | * Represents a SetRegularKey transaction on the XRP Ledger. 5 | * 6 | * A SetRegularKey transaction assigns, changes, or removes the regular key pair associated with an account. 7 | * You can protect your account by assigning a regular key pair to it and using it instead of the master key 8 | * pair to sign transactions whenever possible. If your regular key pair is compromised, but your master key 9 | * pair is not, you can use a SetRegularKey transaction to regain control of your account. 10 | * 11 | * @see: https://xrpl.org/setregularkey.html 12 | */ 13 | export default class XrpSetRegularKey { 14 | /** 15 | * Constructs an XrpSetRegularKey from a SetRegularKey protocol buffer. 16 | * 17 | * @param setRegularKey a SetRegularKey (protobuf object) whose field values will be used to construct an XrpSetRegularKey 18 | * @return an XrpSetRegularKey with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L298 20 | */ 21 | public static from(setRegularKey: SetRegularKey): XrpSetRegularKey { 22 | const regularKey = setRegularKey.getRegularKey()?.getValue()?.getAddress() 23 | return new XrpSetRegularKey(regularKey) 24 | } 25 | 26 | /** 27 | * @param regularKey (Optional) A base-58-encoded Address that indicates the regular key pair to be assigned to the account. 28 | * If omitted, removes any existing regular key pair from the account. 29 | * Must not match the master key pair for the address. 30 | */ 31 | private constructor(readonly regularKey?: string) {} 32 | } 33 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-signer-entry.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import XrpUtils from '../shared/xrp-utils' 3 | import { SignerEntry } from '../Generated/web/org/xrpl/rpc/v1/common_pb' 4 | import { XrplNetwork } from 'xpring-common-js' 5 | 6 | /* 7 | * Represents a SignerEntry object on the XRP Ledger. 8 | * 9 | * @see: https://xrpl.org/signerlist.html#signerentry-object 10 | */ 11 | export default class XrpSignerEntry { 12 | /** 13 | * Constructs an XrpSignerEntry from a SignerEntry protocol buffer. 14 | * 15 | * @param signerEntry a SignerEntry (protobuf object) whose field values will be used to construct an XrpSignerEntry 16 | * @return an XrpSignerEntry with its fields set via the analogous protobuf fields. 17 | * @see https://github.com/ripple/rippled/blob/f43aeda49c5362dc83c66507cae2ec71cfa7bfdf/src/ripple/proto/org/xrpl/rpc/v1/common.proto#L471 18 | */ 19 | public static from( 20 | signerEntry: SignerEntry, 21 | xrplNetwork: XrplNetwork, 22 | ): XrpSignerEntry { 23 | const account = signerEntry.getAccount()?.getValue()?.getAddress() 24 | if (!account) { 25 | throw new XrpError( 26 | XrpErrorType.MalformedProtobuf, 27 | 'SignerEntry protobuf does not contain `account` field.', 28 | ) 29 | } 30 | 31 | const accountXAddress = XrpUtils.encodeXAddress( 32 | account, 33 | undefined, 34 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 35 | ) 36 | if (!accountXAddress) { 37 | throw new XrpError( 38 | XrpErrorType.MalformedProtobuf, 39 | 'Cannot construct XAddress from SignerEntry protobuf `account` field.', 40 | ) 41 | } 42 | 43 | const signerWeight = signerEntry.getSignerWeight()?.getValue() 44 | if (signerWeight === undefined) { 45 | throw new XrpError( 46 | XrpErrorType.MalformedProtobuf, 47 | 'SignerEntry protobuf does not contain `signerWeight` field.', 48 | ) 49 | } 50 | return new XrpSignerEntry(accountXAddress, signerWeight) 51 | } 52 | 53 | /** 54 | * @param account An XRP Ledger address whose signature contributes to the multi-signature, encoded as an 55 | * X-address (see https://xrpaddress.info/). It does not need to be a funded address in the ledger. 56 | * @param signerWeight The weight of a signature from this signer. A multi-signature is only valid if the sum 57 | * weight of the signatures provided meets or exceeds the SignerList's SignerQuorum value. 58 | */ 59 | private constructor( 60 | readonly accountXAddress: string, 61 | readonly signerWeight: number, 62 | ) {} 63 | } 64 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-signer-list-set.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import { SignerListSet } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 4 | import XrpSignerEntry from './xrp-signer-entry' 5 | /* 6 | * Represents a SignerListSet transaction on the XRP Ledger. 7 | * 8 | * A SignerListSet transaction creates, replaces, or removes a list of signers that can be used to multi-sign a 9 | * transaction. This transaction type was introduced by the MultiSign amendment. 10 | * 11 | * @see: https://xrpl.org/signerlistset.html 12 | */ 13 | export default class XrpSignerListSet { 14 | /** 15 | * Constructs an XrpSignerListSet from a SignerListSet protocol buffer. 16 | * 17 | * @param signerListSet a SignerListSet (protobuf object) whose field values will be used to construct an XrpSignerListSet 18 | * @return an XrpSignerListSet with its fields set via the analogous protobuf fields. 19 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L304 20 | */ 21 | public static from( 22 | signerListSet: SignerListSet, 23 | xrplNetwork: XrplNetwork, 24 | ): XrpSignerListSet { 25 | const signerQuorum = signerListSet.getSignerQuorum()?.getValue() 26 | if (signerQuorum === undefined) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedProtobuf, 29 | 'SignerListSet protobuf does not contain `SignerQuorum` field.', 30 | ) 31 | } 32 | const signerEntries = signerListSet 33 | .getSignerEntriesList() 34 | .map((signerEntry) => XrpSignerEntry.from(signerEntry, xrplNetwork)) 35 | if (signerQuorum !== 0) { 36 | if (signerEntries.length === 0) { 37 | throw new XrpError( 38 | XrpErrorType.MalformedProtobuf, 39 | 'SignerListSet protobuf does not contain `signerEntries` field with nonzero `signerQuorum` field.', 40 | ) 41 | } 42 | const maxSignerEntryLength = 8 43 | if (signerEntries.length > maxSignerEntryLength) { 44 | throw new XrpError( 45 | XrpErrorType.MalformedProtobuf, 46 | 'SignerListSet protobuf has greater than 8 members in the `signerEntries` field.', 47 | ) 48 | } 49 | const accounts = new Set() 50 | signerEntries.forEach((signerEntry) => { 51 | const signerAccountXAddress = signerEntry.accountXAddress 52 | if (accounts.has(signerAccountXAddress)) { 53 | throw new XrpError( 54 | XrpErrorType.MalformedProtobuf, 55 | 'SignerListSet protobuf contains repeat account addresses in the `signerEntries` field.', 56 | ) 57 | } 58 | accounts.add(signerAccountXAddress) 59 | }) 60 | } 61 | return new XrpSignerListSet(signerQuorum, signerEntries) 62 | } 63 | 64 | /** 65 | * @param signerQuorum A target number for the signer weights. A multi-signature from this list is valid only if the sum weights 66 | * of the signatures provided is greater than or equal to this value. To delete a SignerList, use the value 0. 67 | * @param signerEntries (Omitted when deleting) Array of XRPSignerEntry objects, indicating the addresses and weights of signers in this list. 68 | * A SignerList must have at least 1 member and no more than 8 members. No address may appear more than once in the list, 69 | * nor may the Account submitting the transaction appear in the list. 70 | */ 71 | private constructor( 72 | readonly signerQuorum: number, 73 | readonly signerEntries?: Array, 74 | ) {} 75 | } 76 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-signer.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | import XrpUtils from '../shared/xrp-utils' 4 | import { Signer } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 5 | 6 | /* 7 | * Represents a signer of a transaction on the XRP Ledger. 8 | * @see: https://xrpl.org/transaction-common-fields.html#signers-field 9 | */ 10 | export default class XrpSigner { 11 | /** 12 | * Constructs an XrpSigner from a Signer. 13 | * 14 | * @param signer a Signer (protobuf object) whose field values will be used 15 | * to construct an XrpSigner 16 | * @return an XrpSigner with its fields set via the analogous protobuf fields. 17 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L90 18 | */ 19 | public static from(signer: Signer, xrplNetwork: XrplNetwork): XrpSigner { 20 | const account = signer.getAccount()?.getValue()?.getAddress() 21 | if (!account) { 22 | throw new XrpError( 23 | XrpErrorType.MalformedProtobuf, 24 | 'Signer protobuf is missing `account` field.', 25 | ) 26 | } 27 | const accountXAddress = XrpUtils.encodeXAddress( 28 | account, 29 | undefined, 30 | xrplNetwork == XrplNetwork.Test || xrplNetwork == XrplNetwork.Dev, 31 | ) 32 | if (!accountXAddress) { 33 | throw new XrpError( 34 | XrpErrorType.MalformedProtobuf, 35 | 'Cannot construct XAddress from Signer protobuf `account` field.', 36 | ) 37 | } 38 | const signingPublicKey = signer.getSigningPublicKey()?.getValue_asU8() 39 | if (!signingPublicKey) { 40 | throw new XrpError( 41 | XrpErrorType.MalformedProtobuf, 42 | 'Signer protobuf is missing `SigningPublicKey` field.', 43 | ) 44 | } 45 | const transactionSignature = signer 46 | .getTransactionSignature() 47 | ?.getValue_asU8() 48 | if (!transactionSignature) { 49 | throw new XrpError( 50 | XrpErrorType.MalformedProtobuf, 51 | 'Signer protobuf is missing `TransactionSignature` field.', 52 | ) 53 | } 54 | return new XrpSigner( 55 | accountXAddress, 56 | signingPublicKey, 57 | transactionSignature, 58 | ) 59 | } 60 | 61 | /** 62 | * @param accountXAddress The address associated with this signature, as it appears in the SignerList, encoded as an 63 | * X-address (see https://xrpaddress.info/). 64 | * @param signingPublicKey The public key used to create this signature. 65 | * @param transactionSignature A signature for this transaction, verifiable using the SigningPubKey. 66 | */ 67 | private constructor( 68 | readonly accountXAddress: string, 69 | readonly signingPublicKey: Uint8Array, 70 | readonly transactionSignature: Uint8Array, 71 | ) {} 72 | } 73 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrp-trust-set.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import { TrustSet } from '../Generated/web/org/xrpl/rpc/v1/transaction_pb' 3 | import XrpCurrencyAmount from './xrp-currency-amount' 4 | 5 | /* 6 | * Represents a TrustSet transaction on the XRP Ledger. 7 | * 8 | * A TrustSet transaction creates or modifies a trust line linking two accounts. 9 | * 10 | * @see: https://xrpl.org/trustset.html 11 | */ 12 | export default class XrpTrustSet { 13 | /** 14 | * Constructs an XrpTrustSet from a TrustSet protocol buffer. 15 | * 16 | * @param trustSet a TrustSet (protobuf object) whose field values will be used to construct an XrpTrustSet 17 | * @return an XrpTrustSet with its fields set via the analogous protobuf fields. 18 | * @see https://github.com/ripple/rippled/blob/3d86b49dae8173344b39deb75e53170a9b6c5284/src/ripple/proto/org/xrpl/rpc/v1/transaction.proto#L312 19 | */ 20 | public static from(trustSet: TrustSet): XrpTrustSet { 21 | const limitAmountCurrencyAmount = trustSet.getLimitAmount()?.getValue() 22 | if (!limitAmountCurrencyAmount) { 23 | throw new XrpError( 24 | XrpErrorType.MalformedProtobuf, 25 | 'TrustSet protobuf missing required field `LimitAmount`.', 26 | ) 27 | } 28 | const limitAmount = XrpCurrencyAmount.from(limitAmountCurrencyAmount) 29 | if (!limitAmount.issuedCurrency) { 30 | throw new XrpError( 31 | XrpErrorType.MalformedProtobuf, 32 | 'TrustSet protobuf does not use issued currency.', 33 | ) 34 | } 35 | const qualityIn = trustSet.getQualityIn()?.getValue() 36 | const qualityOut = trustSet.getQualityOut()?.getValue() 37 | return new XrpTrustSet(limitAmount, qualityIn, qualityOut) 38 | } 39 | 40 | /** 41 | * @param limitAmount Object defining the trust line to create or modify, in the format of an XrpCurrencyAmount. 42 | * limitAmount.currency: The currency this trust line applies to, as a three-letter ISO 4217 Currency Code, 43 | * or a 160-bit hex value according to currency format. "XRP" is invalid. 44 | * limitAmount.value: Quoted decimal representation of the limit to set on this trust line. 45 | * limitAmount.issuer: The address of the account to extend trust to. 46 | * @param qualityIn (Optional) Value incoming balances on this trust line at the ratio of this number per 1,000,000,000 units. 47 | * A value of 0 is shorthand for treating balances at face value. 48 | * @param qualityOut (Optional) Value outgoing balances on this trust line at the ratio of this number per 1,000,000,000 units. 49 | * A value of 0 is shorthand for treating balances at face value. 50 | */ 51 | private constructor( 52 | readonly limitAmount: XrpCurrencyAmount, 53 | readonly qualityIn?: number, 54 | readonly qualityOut?: number, 55 | ) {} 56 | } 57 | -------------------------------------------------------------------------------- /src/XRP/protobuf-wrappers/xrpl-issued-currency.ts: -------------------------------------------------------------------------------- 1 | import { XrpError, XrpErrorType } from '..' 2 | import bigInt, { BigInteger } from 'big-integer' 3 | import { IssuedCurrencyAmount } from '../Generated/web/org/xrpl/rpc/v1/amount_pb' 4 | import XrpCurrency from './xrp-currency' 5 | 6 | /* 7 | * An issued currency on the XRP Ledger 8 | * @see: https://xrpl.org/basic-data-types.html#specifying-currency-amounts 9 | * @see: https://xrpl.org/currency-formats.html#issued-currency-amounts 10 | */ 11 | export default class XrplIssuedCurrency { 12 | /** 13 | * Constructs an XrplIssuedCurrency from an IssuedCurrencyAmount. 14 | * 15 | * @param issuedCurrency an IssuedCurrencyAmount (protobuf object) whose field values will be used 16 | * to construct an XrplIssuedCurrency 17 | * @returns an XrplIssuedCurrency with its fields set via the analogous protobuf fields. 18 | * @see https://github.com/ripple/rippled/blob/develop/src/ripple/proto/org/xrpl/rpc/v1/amount.proto#L28 19 | */ 20 | public static from(issuedCurrency: IssuedCurrencyAmount): XrplIssuedCurrency { 21 | const currency = issuedCurrency.getCurrency() 22 | if (!currency) { 23 | throw new XrpError( 24 | XrpErrorType.MalformedProtobuf, 25 | 'IssuedCurrency protobuf does not contain `currency` field.', 26 | ) 27 | } 28 | 29 | let value 30 | try { 31 | value = bigInt(issuedCurrency.getValue()) 32 | } catch { 33 | throw new XrpError( 34 | XrpErrorType.MalformedProtobuf, 35 | 'Cannot construct BigInt from IssuedCurrency protobuf `value` field.', 36 | ) 37 | } 38 | 39 | const issuer = issuedCurrency.getIssuer()?.getAddress() 40 | if (!issuer) { 41 | throw new XrpError( 42 | XrpErrorType.MalformedProtobuf, 43 | 'IssuedCurrency protobuf does not contain `issuer` field.', 44 | ) 45 | } 46 | return new XrplIssuedCurrency(XrpCurrency.from(currency), value, issuer) 47 | } 48 | 49 | /** 50 | * @param currency The currency used to value the amount. 51 | * @param value The value of the amount. 52 | * @param issuer Unique account address of the entity issuing the currency. 53 | */ 54 | private constructor( 55 | readonly currency: XrpCurrency, 56 | readonly value: BigInteger, 57 | readonly issuer: string, 58 | ) {} 59 | } 60 | -------------------------------------------------------------------------------- /src/XRP/shared/account-root-flags.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise */ 2 | 3 | /** 4 | * There are several options which can be either enabled or disabled for an XRPL account. 5 | * These options can be changed with an AccountSet transaction. In the ledger, 6 | * flags are represented as binary values that can be combined with bitwise-or operations. 7 | * The bit values for the flags in the ledger are different than the values used to enable 8 | * or disable those flags in a transaction. Ledger flags have names that begin with lsf. 9 | * 10 | * @see https://xrpl.org/accountroot.html#accountroot-flags 11 | */ 12 | class AccountRootFlags { 13 | static LSF_DEPOSIT_AUTH = 1 << 24 // 0x01000000, 16777216 14 | static LSF_DEFAULT_RIPPLE = 1 << 23 // 0x00800000, 8388608 15 | static LSF_GLOBAL_FREEZE = 1 << 22 // 0x00400000, 4194304 16 | static LSF_NO_FREEZE = 1 << 21 // 0x00200000, 2097152 17 | static LSF_DISABLE_MASTER = 1 << 20 // 0x00100000, 1048576 18 | static LSF_DISALLOW_XRP = 1 << 19 // 0x00080000, 524288 19 | static LSF_REQUIRE_AUTH = 1 << 18 // 0x00040000, 262144 20 | static LSF_REQUIRE_DEST_TAG = 1 << 17 // 0x00020000, 131072 21 | static LSF_PASSWORD_SPENT = 1 << 16 // 0x00010000, 65536 22 | 23 | /** 24 | * Check if the given flag is set in the given set of bit-flags. 25 | * 26 | * @param flag: The flag to check the presence of. 27 | * @param flags: The flags to check 28 | */ 29 | static checkFlag(flag: number, flags: number): boolean { 30 | return (flag & flags) === flag 31 | } 32 | } 33 | 34 | export default AccountRootFlags 35 | -------------------------------------------------------------------------------- /src/XRP/shared/account-set-flag.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Flags used in AccountSet transactions. 3 | * 4 | * @see https://xrpl.org/accountset.html#accountset-flags 5 | */ 6 | export enum AccountSetFlag { 7 | asfRequireDest = 1, 8 | asfRequireAuth = 2, 9 | asfDisallowXRP = 3, 10 | asfDisableMaster = 4, 11 | asfAccountTxnID = 5, 12 | asfNoFreeze = 6, 13 | asfGlobalFreeze = 7, 14 | asfDefaultRipple = 8, 15 | asfDepositAuth = 9, 16 | } 17 | -------------------------------------------------------------------------------- /src/XRP/shared/gateway-balances.ts: -------------------------------------------------------------------------------- 1 | import { XrpUtils } from 'xpring-common-js' 2 | import { XrpError, XrpErrorType } from '.' 3 | import { GatewayBalancesSuccessfulResponse } from './rippled-web-socket-schema' 4 | 5 | /** 6 | * Represents an amount of an Issued Currency. 7 | */ 8 | export interface IssuedCurrencyValue { 9 | readonly currency: string 10 | readonly value: string 11 | } 12 | 13 | /** 14 | * Represents information about a gateway's issued currency balances on the XRPL, as of the most recent validated ledger version. 15 | * @see https://xrpl.org/gateway_balances.html 16 | */ 17 | export default interface GatewayBalances { 18 | /** 19 | * The address of the account that issued the balances, encoded as an X-Address. 20 | * @see https://xrpaddress.info/ 21 | */ 22 | readonly account: string 23 | 24 | /** 25 | * (Optional, omitted if empty) Total amounts held that are issued by others. 26 | * In the recommended configuration, the issuing address should have none. 27 | */ 28 | readonly assets?: { [account: string]: IssuedCurrencyValue[] } 29 | 30 | /** 31 | * (Optional, omitted if empty) Amounts issued to the excluded addresses from the request. 32 | * The keys are addresses and the values are arrays of currency amounts they hold. 33 | */ 34 | readonly balances?: { [account: string]: IssuedCurrencyValue[] } 35 | 36 | /** 37 | * (Optional, omitted if empty) Total amounts issued to addresses not excluded, as a map of currencies to the total value issued. 38 | */ 39 | readonly obligations?: { [currencyCode: string]: string } 40 | 41 | /** (May be omitted) The ledger index of the ledger version that was used to generate this response. */ 42 | readonly ledgerHash?: string 43 | } 44 | 45 | export function gatewayBalancesFromResponse( 46 | gatewayBalancesResponse: GatewayBalancesSuccessfulResponse, 47 | ): GatewayBalances { 48 | const { result } = gatewayBalancesResponse 49 | 50 | if (!result.validated) { 51 | throw new XrpError( 52 | XrpErrorType.MalformedResponse, 53 | 'Gateway Balances response indicates unvalidated ledger.', 54 | ) 55 | } 56 | const account = result.account 57 | if (!account) { 58 | throw new XrpError( 59 | XrpErrorType.MalformedResponse, 60 | 'gatewayBalancesResponse is missing required field `account`.', 61 | ) 62 | } 63 | const xAddress = XrpUtils.encodeXAddress(account, undefined, true) 64 | if (!xAddress) { 65 | throw new XrpError( 66 | XrpErrorType.MalformedResponse, 67 | 'Could not convert account to X-Address.', 68 | ) 69 | } 70 | 71 | return { 72 | account: xAddress, 73 | ledgerHash: result.ledger_hash, 74 | assets: result.assets, 75 | balances: result.balances, 76 | obligations: result.obligations, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/XRP/shared/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AccountRootFlag } from './account-root-flags' 2 | export { AccountSetFlag } from './account-set-flag' 3 | export { default as PaymentFlag } from './payment-flags' 4 | export { default as RawTransactionStatus } from './raw-transaction-status' 5 | export { default as SendXrpDetails } from './send-xrp-details' 6 | export { default as TransactionResult } from './transaction-result' 7 | export { default as TransactionStatus } from './transaction-status' 8 | export { default as XrpError } from './xrp-error' 9 | export { XrpErrorType } from './xrp-error' 10 | export { default as XrpTransactionType } from './xrp-transaction-type' 11 | export { default as XrpUtils } from './xrp-utils' 12 | -------------------------------------------------------------------------------- /src/XRP/shared/issued-currency.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents an issued currency on the XRP Ledger. 3 | * @see https://xrpl.org/currency-formats.html#issued-currency-amounts 4 | * 5 | * @property currency - Arbitrary three-letter code for currency to issue. Cannot be XRP. 6 | * @property issuer - The unique XRPL account address of the entity issuing the currency. 7 | * @property value - Quoted decimal representation of the amount of currency. 8 | */ 9 | export default interface IssuedCurrency { 10 | currency: string 11 | issuer: string 12 | value: string 13 | } 14 | -------------------------------------------------------------------------------- /src/XRP/shared/offer-create-flag.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Flags used in OfferCreate transactions. 3 | * 4 | * @see https://xrpl.org/offercreate.html#offercreate-flags 5 | */ 6 | enum OfferCreateFlag { 7 | TF_PASSIVE = 0x00010000, 8 | TF_IMMEDIATE_OR_CANCEL = 0x00020000, 9 | TF_FILL_OR_KILL = 0x00040000, 10 | TF_SELL = 0x00080000, 11 | } 12 | 13 | export default OfferCreateFlag 14 | -------------------------------------------------------------------------------- /src/XRP/shared/payment-flags.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise */ 2 | 3 | /** 4 | * Flags used in payment transactions. 5 | * 6 | * @note These are only flags which are utilized in Xpring SDK. 7 | * For the full list of payment flags, @see https://xrpl.org/payment.html#payment-flags 8 | * 9 | */ 10 | class PaymentFlags { 11 | static TF_PARTIAL_PAYMENT = 1 << 17 12 | 13 | /** 14 | * Check if the given flag is set in the given set of bit-flags. 15 | * 16 | * @param flag: The flag to check the presence of. 17 | * @param flags: The flags to check 18 | */ 19 | static checkFlag(flag: number, flags: number): boolean { 20 | return (flag & flags) === flag 21 | } 22 | } 23 | 24 | export default PaymentFlags 25 | -------------------------------------------------------------------------------- /src/XRP/shared/raw-transaction-status.ts: -------------------------------------------------------------------------------- 1 | import { GetTransactionResponse } from '../Generated/web/org/xrpl/rpc/v1/get_transaction_pb' 2 | import PaymentFlags from './payment-flags' 3 | import { XrpError, XrpErrorType } from '.' 4 | 5 | /** Abstraction around raw Transaction Status for compatibility. */ 6 | // TODO:(keefertaylor) This class is now defunct. Refactor and remove. 7 | export default class RawTransactionStatus { 8 | /** 9 | * Create a RawTransactionStatus from a GetTransactionResponse protocol buffer. 10 | */ 11 | static fromGetTransactionResponse( 12 | getTransactionResponse: GetTransactionResponse, 13 | ): RawTransactionStatus { 14 | const transaction = getTransactionResponse.getTransaction() 15 | if (!transaction) { 16 | throw new XrpError( 17 | XrpErrorType.MalformedResponse, 18 | 'Malformed input, `getTxResponse` did not contain a transaction.', 19 | ) 20 | } 21 | 22 | const transactionResultCode = getTransactionResponse 23 | .getMeta() 24 | ?.getTransactionResult() 25 | ?.getResult() 26 | if (!transactionResultCode) { 27 | throw new XrpError( 28 | XrpErrorType.MalformedResponse, 29 | 'Malformed input, `getTxResponse` did not contain a transaction result code.', 30 | ) 31 | } 32 | const isPayment = transaction.hasPayment() 33 | const flags = transaction.getFlags()?.getValue() ?? 0 34 | 35 | const isPartialPayment = PaymentFlags.checkFlag( 36 | PaymentFlags.TF_PARTIAL_PAYMENT, 37 | flags, 38 | ) 39 | 40 | const isFullPayment = isPayment && !isPartialPayment 41 | 42 | return new RawTransactionStatus( 43 | getTransactionResponse.getValidated(), 44 | transactionResultCode, 45 | getTransactionResponse 46 | .getTransaction() 47 | ?.getLastLedgerSequence() 48 | ?.getValue(), 49 | isFullPayment, 50 | ) 51 | } 52 | 53 | /** 54 | * Note: This constructor is exposed for testing purposes. Clients of this code should favor using a static factory method. 55 | */ 56 | constructor( 57 | public isValidated: boolean, 58 | public transactionStatusCode: string, 59 | public lastLedgerSequence: number | undefined, 60 | public isFullPayment: boolean, 61 | ) {} 62 | } 63 | -------------------------------------------------------------------------------- /src/XRP/shared/rippled-error-messages.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An enum for creating constants that represent possible error messages received from the rippled WebSocket API. 3 | */ 4 | export enum RippledErrorMessages { 5 | accountNotFound = 'actNotFound', 6 | invalidExcludedAddress = 'invalidHotWallet', 7 | } 8 | -------------------------------------------------------------------------------- /src/XRP/shared/send-xrp-details.ts: -------------------------------------------------------------------------------- 1 | import { BigInteger } from 'big-integer' 2 | import { Wallet } from 'xpring-common-js' 3 | import XrpMemo from '../protobuf-wrappers/xrp-memo' 4 | 5 | /** 6 | * Describes the fine grained details for sending money over the XRP ledger. The 7 | * destination field may be a PayID, XAddress, or other type of address. Handling 8 | * of the given destination type is the responsibility of the client. 9 | */ 10 | export default interface SendXrpDetails { 11 | amount: BigInteger | number | string 12 | destination: string 13 | sender: Wallet 14 | 15 | memoList?: Array 16 | } 17 | -------------------------------------------------------------------------------- /src/XRP/shared/transaction-prefix.ts: -------------------------------------------------------------------------------- 1 | /** Represents prefixes of transaction statuses. */ 2 | enum TransactionPrefix { 3 | ClaimedCostOnly = 'tec', 4 | Failure = 'tef', 5 | LocalError = 'tel', 6 | MalformedTransaction = 'tem', 7 | Retry = 'ter', 8 | Success = 'tes', 9 | } 10 | 11 | export default TransactionPrefix 12 | -------------------------------------------------------------------------------- /src/XRP/shared/transaction-result.ts: -------------------------------------------------------------------------------- 1 | import TransactionStatus from '../shared/transaction-status' 2 | 3 | /** 4 | * Represents the outcome of submitting an XRPL transaction. 5 | */ 6 | export default class TransactionResult { 7 | /** 8 | * Constructs a TransactionResult in the case where the result is not final. 9 | * The outcome of a transaction is not final if it has not yet been included in 10 | * a validated ledger but still has a valid lastLedgerSequence field. 11 | * @see https://xrpl.org/reliable-transaction-submission.html#lastledgersequence 12 | * 13 | * @param hash The identifying hash of the transaction. 14 | * @param status The result of the transaction. 15 | * @param validated Whether this transaction (and status) are included in a validated ledger. 16 | */ 17 | public static createPendingTransactionResult( 18 | hash: string, 19 | status: TransactionStatus, 20 | validated: boolean, 21 | ): TransactionResult { 22 | return new TransactionResult(hash, status, validated, false) 23 | } 24 | 25 | /** 26 | * Constructs a TransactionResult in the case where the result is final. 27 | * The outcome of a transaction is final if it has been included in a validated 28 | * ledger, or has not been included in a validated ledger but has a lastLedgerSequence field 29 | * that has been equaled or surpassed by the sequence number of the latest validated ledger. 30 | * @see https://xrpl.org/reliable-transaction-submission.html#lastledgersequence 31 | * 32 | * @param hash The identifying hash of the transaction. 33 | * @param status The result of the transaction. 34 | * @param validated Whether this transaction (and status) are included in a validated ledger. 35 | */ 36 | public static createFinalTransactionResult( 37 | hash: string, 38 | status: TransactionStatus, 39 | validated: boolean, 40 | ): TransactionResult { 41 | return new TransactionResult(hash, status, validated, true) 42 | } 43 | 44 | /** 45 | * @param hash The identifying hash of the transaction. 46 | * @param status The result of the transaction. 47 | * @param validated Whether this transaction (and status) are included in a validated ledger. 48 | * @param final Whether this transaction result is the final outcome of the transaction on the XRPL. 49 | * The `status` and `validated` fields are subject to change unless this field is true. 50 | */ 51 | private constructor( 52 | public readonly hash: string, 53 | public readonly status: TransactionStatus, 54 | public readonly validated: boolean, 55 | public readonly final: boolean, 56 | ) {} 57 | } 58 | -------------------------------------------------------------------------------- /src/XRP/shared/transaction-status.ts: -------------------------------------------------------------------------------- 1 | /** Represents statuses of transactions. */ 2 | enum TransactionStatus { 3 | /** 4 | * The transaction failed because the provided paths did not have enough liquidity to send anything at all. 5 | * This could mean that the source and destination accounts are not linked by trust lines. 6 | * The transaction cost was destroyed. 7 | */ 8 | ClaimedCostOnly_PathDry, 9 | 10 | /** 11 | * The transaction failed because the provided paths did not have enough liquidity to send the full amount. 12 | * The transaction cost was destroyed. 13 | */ 14 | ClaimedCostOnly_PathPartial, 15 | 16 | /** The transaction did not achieve its intended purpose, but the transaction cost was destroyed. */ 17 | ClaimedCostOnly, 18 | 19 | /** The transaction is not included in a finalized ledger, and is not valid, due to improper syntax, conflicting options, a bad signature, or something else. */ 20 | MalformedTransaction, 21 | 22 | /** The transaction was included in a finalized ledger and failed. */ 23 | Failed, 24 | 25 | /** The transaction is not included in a finalized ledger. */ 26 | Pending, 27 | 28 | /** The transaction was included in a finalized ledger and succeeded. */ 29 | Succeeded, 30 | 31 | /** The transaction's last ledger sequence has been surpassed; the transaction will never be included in a validated ledger. */ 32 | LastLedgerSequenceExpired, 33 | 34 | /** The transaction status is unknown. */ 35 | Unknown, 36 | } 37 | 38 | export default TransactionStatus 39 | -------------------------------------------------------------------------------- /src/XRP/shared/trust-set-flag.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Flags used in TrustSet transactions. 3 | * 4 | * @see https://xrpl.org/trustset.html#trustset-flags 5 | */ 6 | enum TrustSetFlag { 7 | tfSetfAuth = 0x00010000, 8 | tfSetNoRipple = 0x00020000, 9 | tfClearNoRipple = 0x00040000, 10 | tfSetFreeze = 0x00100000, 11 | tfClearFreeze = 0x00200000, 12 | } 13 | 14 | export default TrustSetFlag 15 | -------------------------------------------------------------------------------- /src/XRP/shared/trustline.ts: -------------------------------------------------------------------------------- 1 | import { TrustLineResponse } from './rippled-web-socket-schema' 2 | 3 | /** 4 | * Represents a trust line on the XRP Ledger. 5 | * @see https://xrpl.org/trust-lines-and-issuing.html 6 | */ 7 | export default class TrustLine { 8 | /** The unique Address of the counterparty to this trust line. */ 9 | readonly account: string 10 | 11 | /** 12 | * Representation of the numeric balance currently held against this line. 13 | * A positive balance means that the perspective account holds value; 14 | * a negative balance means that the perspective account owes value. 15 | */ 16 | readonly balance: string 17 | 18 | /** 19 | * A Currency Code identifying what currency this trust line can hold. 20 | * @see https://xrpl.org/currency-formats.html#currency-codes 21 | */ 22 | readonly currency: string 23 | 24 | /** The maximum amount of the given currency that this account is willing to owe the peer account. */ 25 | readonly limit: string 26 | 27 | /** The maximum amount of currency that the counterparty account is willing to owe the perspective account. */ 28 | readonly limitPeer: string 29 | 30 | /** 31 | * Rate at which the account values incoming balances on this trust line, as a ratio of this value per 1 billion units. 32 | * (For example, a value of 500 million represents a 0.5:1 ratio.) As a special case, 0 is treated as a 1:1 ratio. 33 | */ 34 | readonly qualityIn: number 35 | 36 | /** 37 | * Rate at which the account values outgoing balances on this trust line, as a ratio of this value per 1 billion units. 38 | * (For example, a value of 500 million represents a 0.5:1 ratio.) As a special case, 0 is treated as a 1:1 ratio. 39 | */ 40 | readonly qualityOut: number 41 | 42 | /** True if this account has enabled the No Ripple flag for this line, otherwise false. 43 | * @see https://xrpl.org/rippling.html 44 | */ 45 | readonly noRipple: boolean 46 | 47 | /** True if the peer account has enabled the No Ripple flag, otherwise false. 48 | * @see https://xrpl.org/rippling.html 49 | */ 50 | readonly noRipplePeer: boolean 51 | 52 | /** 53 | * True if this account has authorized this trust line, otherwise false. 54 | * @see https://xrpl.org/authorized-trust-lines.html 55 | */ 56 | readonly authorized: boolean 57 | 58 | /** 59 | * True if the peer account has authorized this trust line, otherwise false. 60 | * @see https://xrpl.org/authorized-trust-lines.html 61 | */ 62 | readonly peerAuthorized: boolean 63 | 64 | /** True if this account has frozen this trust line, otherwise false. 65 | * @see https://xrpl.org/freezes.html 66 | */ 67 | readonly freeze: boolean 68 | 69 | /** True if the peer account has frozen this trust line, otherwise false. 70 | * @see https://xrpl.org/freezes.html 71 | */ 72 | readonly freezePeer: boolean 73 | 74 | public constructor(trustLineResponse: TrustLineResponse) { 75 | this.account = trustLineResponse.account 76 | this.balance = trustLineResponse.balance 77 | this.currency = trustLineResponse.currency 78 | this.limit = trustLineResponse.limit 79 | this.limitPeer = trustLineResponse.limit_peer 80 | this.qualityIn = trustLineResponse.quality_in 81 | this.qualityOut = trustLineResponse.quality_out 82 | this.noRipple = !!trustLineResponse.no_ripple 83 | this.noRipplePeer = !!trustLineResponse.no_ripple_peer 84 | this.authorized = !!trustLineResponse.authorized 85 | this.peerAuthorized = !!trustLineResponse.peer_authorized 86 | this.freeze = !!trustLineResponse.freeze 87 | this.freezePeer = !!trustLineResponse.freeze_peer 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/XRP/shared/xrp-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types of errors that originate from interacting with XRPL. 3 | */ 4 | export enum XrpErrorType { 5 | InvalidInput, 6 | PaymentConversionFailure, 7 | MalformedProtobuf, 8 | MalformedResponse, 9 | AccountNotFound, 10 | SigningError, 11 | Unknown, 12 | XAddressRequired, 13 | NoViablePaths, 14 | } 15 | 16 | /** 17 | * Represents errors thrown by XRP components of the Xpring SDK. 18 | */ 19 | export default class XrpError extends Error { 20 | /** 21 | * An X-Address is required to use the requested functionality. 22 | */ 23 | static xAddressRequired = new XrpError( 24 | XrpErrorType.XAddressRequired, 25 | 'Please use the X-Address format. See: https://xrpaddress.info/.', 26 | ) 27 | 28 | /** 29 | * A payment transaction can't be converted to an XrpTransaction. 30 | */ 31 | static paymentConversionFailure = new XrpError( 32 | XrpErrorType.PaymentConversionFailure, 33 | 'Could not convert payment transaction: (transaction). Please file a bug at https://github.com/xpring-eng/Xpring-JS/issues.', 34 | ) 35 | 36 | /** 37 | * Encountered a protocol buffer formatted in contradiction to the logic of the XRPL. 38 | * @see xrpl.org for XRPL documentation. 39 | */ 40 | static malformedProtobuf = new XrpError( 41 | XrpErrorType.MalformedProtobuf, 42 | 'Encountered a protocol buffer in unexpected format. See xrpl.org for XRPL documentation.', 43 | ) 44 | 45 | /** 46 | * The response was in an unexpected format. 47 | */ 48 | static malformedResponse = new XrpError( 49 | XrpErrorType.MalformedResponse, 50 | 'The response from the remote service was malformed or in an unexpected format.', 51 | ) 52 | 53 | /** 54 | * The account could not be found on the XRPL. 55 | */ 56 | static accountNotFound = new XrpError( 57 | XrpErrorType.AccountNotFound, 58 | 'The requested account could not be found on the XRPL.', 59 | ) 60 | 61 | /** 62 | * There was a problem signing the transaction. 63 | */ 64 | static signingError = new XrpError( 65 | XrpErrorType.SigningError, 66 | 'There was an error signing the transaction.', 67 | ) 68 | 69 | /** 70 | * @param errorType The type of error. 71 | * @param message The error message. 72 | */ 73 | constructor( 74 | public readonly errorType: XrpErrorType, 75 | message: string | undefined = undefined, 76 | ) { 77 | super(message) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/XRP/shared/xrp-transaction-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types of transactions on the XRP Ledger. 3 | * 4 | * This is a partial list. Please file an issue if you have a use case that requires additional types. 5 | * 6 | * @see: https://xrpl.org/transaction-formats.html 7 | */ 8 | enum XrpTransactionType { 9 | Payment, 10 | AccountSet, 11 | AccountDelete, 12 | CheckCancel, 13 | CheckCash, 14 | CheckCreate, 15 | DepositPreauth, 16 | EscrowCancel, 17 | EscrowCreate, 18 | EscrowFinish, 19 | OfferCancel, 20 | OfferCreate, 21 | PaymentChannelClaim, 22 | PaymentChannelCreate, 23 | PaymentChannelFund, 24 | SetRegularKey, 25 | SignerListSet, 26 | TrustSet, 27 | } 28 | 29 | export default XrpTransactionType 30 | -------------------------------------------------------------------------------- /src/XRP/shared/xrp-utils.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { BigNumber } from 'bignumber.js' 3 | import XrpError, { XrpErrorType } from './xrp-error' 4 | import { XrpUtils } from 'xpring-common-js' 5 | 6 | function dropsToXrp(drops: BigNumber.Value): string { 7 | const dropsRegEx = RegExp(/^-?[0-9]*\.?[0-9]*$/) 8 | 9 | if (typeof drops === 'string') { 10 | if (!dropsRegEx.exec(drops)) { 11 | throw new XrpError( 12 | XrpErrorType.InvalidInput, 13 | `dropsToXrp: invalid value '${drops}',` + 14 | ` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`, 15 | ) 16 | } else if (drops === '.') { 17 | throw new XrpError( 18 | XrpErrorType.InvalidInput, 19 | `dropsToXrp: invalid value '${drops}',` + 20 | ` should be a BigNumber or string-encoded number.`, 21 | ) 22 | } 23 | } 24 | 25 | // Converting to BigNumber and then back to string should remove any 26 | // decimal point followed by zeros, e.g. '1.00'. 27 | // Important: specify base 10 to avoid exponential notation, e.g. '1e-7'. 28 | // eslint-disable-next-line no-param-reassign 29 | drops = new BigNumber(drops).toString(10) 30 | 31 | // drops are only whole units 32 | if (drops.includes('.')) { 33 | throw new XrpError( 34 | XrpErrorType.InvalidInput, 35 | `dropsToXrp: value '${drops}' has too many decimal places.`, 36 | ) 37 | } 38 | 39 | // This should never happen; the value has already been 40 | // validated above. This just ensures BigNumber did not do 41 | // something unexpected. 42 | 43 | if (!dropsRegEx.exec(drops)) { 44 | throw new XrpError( 45 | XrpErrorType.InvalidInput, 46 | `dropsToXrp: failed sanity check -` + 47 | ` value '${drops}',` + 48 | ` does not match (^-?[0-9]+$).`, 49 | ) 50 | } 51 | 52 | return new BigNumber(drops).dividedBy(1000000.0).toString(10) 53 | } 54 | 55 | function xrpToDrops(xrp: BigNumber.Value): string { 56 | const xrpRegEx = RegExp(/^-?[0-9]*\.?[0-9]*$/) 57 | 58 | if (typeof xrp === 'string') { 59 | // eslint-disable-next-line @typescript-eslint/prefer-regexp-exec 60 | if (!xrpRegEx.exec(xrp)) { 61 | throw new XrpError( 62 | XrpErrorType.InvalidInput, 63 | `xrpToDrops: invalid value '${xrp}',` + 64 | ` should be a number matching (^-?[0-9]*\\.?[0-9]*$).`, 65 | ) 66 | } else if (xrp === '.') { 67 | throw new XrpError( 68 | XrpErrorType.InvalidInput, 69 | `xrpToDrops: invalid value '${xrp}',` + 70 | ` should be a BigNumber or string-encoded number.`, 71 | ) 72 | } 73 | } 74 | 75 | // Important: specify base 10 to avoid exponential notation, e.g. '1e-7'. 76 | // eslint-disable-next-line no-param-reassign 77 | xrp = new BigNumber(xrp).toString(10) 78 | 79 | // This should never happen; the value has already been 80 | // validated above. This just ensures BigNumber did not do 81 | // something unexpected. 82 | if (!xrpRegEx.exec(xrp)) { 83 | throw new XrpError( 84 | XrpErrorType.InvalidInput, 85 | `xrpToDrops: failed sanity check -` + 86 | ` value '${xrp}',` + 87 | ` does not match (^-?[0-9.]+$).`, 88 | ) 89 | } 90 | 91 | const components = xrp.split('.') 92 | if (components.length > 2) { 93 | throw new XrpError( 94 | XrpErrorType.InvalidInput, 95 | `xrpToDrops: failed sanity check -` + 96 | ` value '${xrp}' has` + 97 | ` too many decimal points.`, 98 | ) 99 | } 100 | 101 | const fraction = components[1] || '0' 102 | if (fraction.length > 6) { 103 | throw new XrpError( 104 | XrpErrorType.InvalidInput, 105 | `xrpToDrops: value '${xrp}' has too many decimal places.`, 106 | ) 107 | } 108 | 109 | return new BigNumber(xrp) 110 | .times(1000000.0) 111 | .integerValue(BigNumber.ROUND_FLOOR) 112 | .toString(10) 113 | } 114 | 115 | function isString(value: unknown): boolean { 116 | return typeof value === 'string' 117 | } 118 | 119 | /** 120 | * A type guard for the IssuedCurrency interface. 121 | * 122 | * @param object The object to check for conformance to the IssuedCurrency interface. 123 | */ 124 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 125 | function isIssuedCurrency(object: any): boolean { 126 | if (!isString(object)) { 127 | return 'currency' in object && 'issuer' in object && 'value' in object 128 | } 129 | return false 130 | } 131 | 132 | export default { 133 | dropsToXrp, 134 | xrpToDrops, 135 | isString, 136 | isIssuedCurrency, 137 | ...XrpUtils, 138 | } 139 | -------------------------------------------------------------------------------- /src/Xpring/xpring-client.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from 'xpring-common-js' 2 | import { BigInteger } from 'big-integer' 3 | import XrpPayIdClientInterface from '../PayID/xrp-pay-id-client-interface' 4 | import XrpClientInterface from '../XRP/xrp-client-interface' 5 | import XpringError from './xpring-error' 6 | import SendXrpDetails from '../XRP/shared/send-xrp-details' 7 | 8 | /** 9 | * Composes interactions of Xpring services. 10 | */ 11 | export default class XpringClient { 12 | private readonly payIdClient: XrpPayIdClientInterface 13 | 14 | /** 15 | * Create a new XpringClient. 16 | * 17 | * @param payIdClient A PayID Client used to interact with the PayID protocol. 18 | * @param xrpClient An XRP Client used to interact with the XRP Ledger protocol. 19 | * @throws A XpringError if the networks of the inputs do not match. 20 | */ 21 | constructor( 22 | payIdClient: XrpPayIdClientInterface, 23 | private readonly xrpClient: XrpClientInterface, 24 | ) { 25 | this.payIdClient = payIdClient 26 | 27 | // Verify that networks match. 28 | const payIdNetwork = payIdClient.xrplNetwork 29 | const xrpNetwork = xrpClient.network 30 | if (payIdNetwork !== xrpNetwork) { 31 | throw XpringError.mismatchedNetworks 32 | } 33 | } 34 | 35 | /** 36 | * Send the given amount of XRP from the source wallet to the destination PayID. 37 | * 38 | * @param amount A `BigInteger`, number or numeric string representing the number of drops to send. 39 | * @param destinationPayID A destination PayID to send the drops to. 40 | * @param sender The wallet that XRP will be sent from and which will sign the request. 41 | * @returns A promise which resolves to a string representing the hash of the submitted transaction. 42 | */ 43 | public async send( 44 | amount: BigInteger | number | string, 45 | destinationPayID: string, 46 | sender: Wallet, 47 | ): Promise { 48 | return this.sendWithDetails({ 49 | amount, 50 | destination: destinationPayID, 51 | sender, 52 | }) 53 | } 54 | 55 | /** 56 | * Send the given amount of XRP from the source wallet to the destination PayID, allowing 57 | * for additional details to be specified for use with supplementary features of the XRP 58 | * ledger. 59 | * 60 | * @param sendXrpDetails - a wrapper object containing details for constructing a transaction. 61 | * @returns A promise which resolves to a string representing the hash of the submitted transaction. 62 | */ 63 | public async sendWithDetails( 64 | sendXrpDetails: SendXrpDetails, 65 | ): Promise { 66 | const { 67 | amount, 68 | destination: destinationPayID, 69 | sender, 70 | memoList, 71 | } = sendXrpDetails 72 | // Resolve the destination address to an XRP address. 73 | const destinationAddress = await this.payIdClient.xrpAddressForPayId( 74 | destinationPayID, 75 | ) 76 | 77 | // Transact XRP to the resolved address. 78 | const transactionResult = await this.xrpClient.sendXrpWithDetails({ 79 | amount, 80 | destination: destinationAddress, 81 | sender, 82 | memoList, 83 | }) 84 | 85 | return transactionResult.hash 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Xpring/xpring-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Types of errors that originate from Xpring. 3 | */ 4 | export enum XpringErrorType { 5 | MismatchedNetworks, 6 | Unknown, 7 | } 8 | 9 | /** 10 | * Represents errors thrown by Xpring components of the Xpring SDK. 11 | */ 12 | export default class XpringError extends Error { 13 | /** 14 | * Input entities given to a Xpring component were attached to different networks. 15 | * 16 | * For instance, this error may be thrown if a XpringClient was constructed with 17 | * a PayIDClient attached to Testnet and an XrpClient attached to Mainnet. 18 | */ 19 | public static mismatchedNetworks = new XpringError( 20 | XpringErrorType.MismatchedNetworks, 21 | 'Components are not connecting to the same network.', 22 | ) 23 | 24 | /** 25 | * @param errorType The type of error. 26 | * @param message The error message. 27 | */ 28 | public constructor( 29 | public readonly errorType: XpringErrorType, 30 | message: string | undefined = undefined, 31 | ) { 32 | super(message) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** Re-exports from xpring-common-js. */ 2 | export { 3 | ClassicAddress, 4 | PayIdUtils, 5 | Wallet, 6 | WalletGenerationResult, 7 | Utils, 8 | XrplNetwork, 9 | } from 'xpring-common-js' 10 | 11 | /** XRP Functionality. */ 12 | export { 13 | XrpClient, 14 | IssuedCurrencyClient, 15 | TrustLine, 16 | GatewayBalances, 17 | } from './XRP' 18 | // export { IssuedCurrency } from './XRP' 19 | export { 20 | XrpCurrencyAmount, 21 | XrpCurrency, 22 | XrplIssuedCurrency, 23 | XrpMemo, 24 | XrpPathElement, 25 | XrpPath, 26 | XrpPayment, 27 | XrpSigner, 28 | XrpTransaction, 29 | } from './XRP/protobuf-wrappers' 30 | export { 31 | AccountSetFlag, 32 | AccountRootFlag, 33 | PaymentFlag, 34 | SendXrpDetails, 35 | TransactionResult, 36 | TransactionStatus, 37 | XrpError, 38 | XrpTransactionType, 39 | XrpUtils, 40 | } from './XRP/shared' 41 | export { default as XrpPayIdClient } from './PayID/xrp-pay-id-client' 42 | 43 | /** PayID Functionality. */ 44 | export { PayIdErrorType, default as PayIdError } from './PayID/pay-id-error' 45 | export { default as PayIdClient } from './PayID/pay-id-client' 46 | 47 | /** ILP Functionality. */ 48 | export { PaymentRequest, PaymentResult, AccountBalance, IlpClient } from './ILP' 49 | export { default as IlpError, IlpErrorType } from './ILP/ilp-error' 50 | 51 | /** Xpring Functionality. */ 52 | export { default as XpringClient } from './Xpring/xpring-client' 53 | -------------------------------------------------------------------------------- /test/Common/Fakes/fake-grpc-error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Convenience class for creating more specific Error objects to mimick gRPC error responses. 3 | */ 4 | export default class FakeGRPCError extends Error { 5 | /** 6 | * Construct a new FakeServiceError. 7 | * 8 | * @param message The text details of the error. 9 | * @param code The gRPC status code. 10 | */ 11 | constructor( 12 | public readonly code: number, 13 | public readonly message: string = '', 14 | ) { 15 | super(message) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/Common/Helpers/result.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A result monad for a type T. Either an instance of T, or an error. 3 | */ 4 | type Result = T | Error 5 | 6 | export default Result 7 | -------------------------------------------------------------------------------- /test/ILP/fakes/fake-default-ilp-client.ts: -------------------------------------------------------------------------------- 1 | import DefaultIlpClient from '../../../src/ILP/default-ilp-client' 2 | import { FakeIlpNetworkClient } from './fake-ilp-network-client' 3 | 4 | /** 5 | * Contains all of the fake DefaultIlpClients needed by DefaultIlpClient Tests. 6 | */ 7 | export default class FakeDefaultIlpClient { 8 | /** 9 | * Creates a {@link DefaultIlpClient} with network clients that will always respond with the specified error. 10 | * 11 | * @param responseError The error that will be returned by the resulting {@link DefaultIlpClient}'s 12 | * {@link IlpNetworkClient} 13 | * @return a {@link DefaultIlpClient} with network clients that always return responseError 14 | */ 15 | public static withErrors(responseError: Error): DefaultIlpClient { 16 | return new DefaultIlpClient(FakeIlpNetworkClient.withErrors(responseError)) 17 | } 18 | 19 | /** 20 | * A DefaultIlpClient with a FakeIlpNetworkClient that always succeeds. 21 | */ 22 | public static fakeSuceedingNetworkClient = (): DefaultIlpClient => { 23 | return new DefaultIlpClient(new FakeIlpNetworkClient()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/ILP/ilp-credentials-web.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { fail } from 'assert' 3 | import IlpCredentials from '../../src/ILP/auth/ilp-credentials.web' 4 | import IlpError, { IlpErrorType } from '../../src/ILP/ilp-error' 5 | 6 | describe('IlpCredentials Web', function (): void { 7 | it('Build - Undefined token', function (): void { 8 | // GIVEN no bearer token 9 | // WHEN IlpCredentials are built without a bearer token 10 | const credentials = IlpCredentials.build() 11 | 12 | // THEN credentials should be undefined 13 | assert(!credentials) 14 | }) 15 | 16 | it('Build - no prefix', function (): void { 17 | // GIVEN a bearer token with no "Bearer " prefix 18 | const accessToken = 'password' 19 | 20 | // WHEN IlpCredentials are built 21 | const credentials = IlpCredentials.build(accessToken) 22 | 23 | // THEN "Bearer " is added to token 24 | assert.equal( 25 | credentials && credentials.Authorization, 26 | 'Bearer '.concat(accessToken), 27 | ) 28 | }) 29 | 30 | it('Build - with prefix', function (): void { 31 | // GIVEN a bearer token with a "Bearer " prefix 32 | const accessToken = 'Bearer password' 33 | 34 | // WHEN IlpCredentials are built 35 | try { 36 | IlpCredentials.build(accessToken) 37 | fail() 38 | } catch (error) { 39 | // THEN and Error is thrown 40 | assert.equal( 41 | (error as IlpError).errorType, 42 | IlpErrorType.InvalidAccessToken, 43 | ) 44 | assert.equal(error.message, IlpError.invalidAccessToken.message) 45 | } 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/ILP/ilp-credentials.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { fail } from 'assert' 3 | import IlpCredentials from '../../src/ILP/auth/ilp-credentials' 4 | import IlpError, { IlpErrorType } from '../../src/ILP/ilp-error' 5 | 6 | describe('IlpCredentials Node', function (): void { 7 | it('Build - Undefined token', function (): void { 8 | // GIVEN no bearer token 9 | // WHEN IlpCredentials are built without a bearer token 10 | const credentials = IlpCredentials.build() 11 | 12 | // THEN credentials should have an Authorization key 13 | // AND it should equal 'Bearer ' 14 | assert.equal(credentials.get('Authorization')[0], 'Bearer ') 15 | }) 16 | 17 | it('Build - no prefix', function (): void { 18 | // GIVEN a bearer token with no "Bearer " prefix 19 | const accessToken = 'password' 20 | 21 | // WHEN IlpCredentials are built 22 | const credentials = IlpCredentials.build(accessToken) 23 | 24 | // THEN "Bearer " is added to token 25 | assert.equal( 26 | credentials.get('Authorization')[0], 27 | 'Bearer '.concat(accessToken), 28 | ) 29 | }) 30 | 31 | it('Build - with prefix', function (): void { 32 | // GIVEN a bearer token with a "Bearer " prefix 33 | const accessToken = 'Bearer password' 34 | 35 | // WHEN IlpCredentials are built 36 | try { 37 | IlpCredentials.build(accessToken) 38 | fail() 39 | } catch (error) { 40 | // THEN an Error is thrown 41 | assert.equal( 42 | (error as IlpError).errorType, 43 | IlpErrorType.InvalidAccessToken, 44 | ) 45 | assert.equal(error.message, IlpError.invalidAccessToken.message) 46 | } 47 | }) 48 | }) 49 | -------------------------------------------------------------------------------- /test/ILP/ilp-integration.test.ts: -------------------------------------------------------------------------------- 1 | import bigInt from 'big-integer' 2 | import { assert } from 'chai' 3 | import IlpClient from '../../src/ILP/ilp-client' 4 | import { PaymentRequest } from '../../src/ILP/model/payment-request' 5 | import IlpError, { IlpErrorType } from '../../src/ILP/ilp-error' 6 | 7 | // A timeout for these tests. 8 | const timeoutMs = 60 * 3000 // 3 minutes 9 | 10 | // A ILP Client that makes requests. 11 | const ILPAddress = 'stg.grpcng.wallet.xpring.io' 12 | const ILPClientNode = new IlpClient(ILPAddress) 13 | 14 | describe('ILP Integration Tests', function (): void { 15 | it("Get Bob's Account Balance - Node Shim", async function (): Promise { 16 | this.timeout(timeoutMs) 17 | // GIVEN an account on the devnet connector with accountId = sdk_account1 18 | 19 | // WHEN the balance of that account is requested 20 | const message = await ILPClientNode.getBalance('sdk_account1', 'password') 21 | 22 | // THEN accountId should be sdk_account1 23 | assert.equal(message.accountId, 'sdk_account1') 24 | // AND assetCode should be XRP 25 | assert.equal(message.assetCode, 'XRP') 26 | // AND assetScale should be 9 27 | assert.equal(message.assetScale, 9) 28 | // AND netBalance should be less than 0, since sdk_account1 always sends money in the Send Money test 29 | assert(message.netBalance.lesserOrEquals(0)) 30 | // AND clearingBalance should be less than 0, since sdk_account1 always sends money in the Send Money test 31 | assert(message.clearingBalance.lesserOrEquals(0)) 32 | // AND prepaidAmount should be 0 33 | assert.equal(message.prepaidAmount.valueOf(), 0) 34 | }) 35 | 36 | it('Get Account Balance - Bearer Token', async function (): Promise { 37 | this.timeout(timeoutMs) 38 | 39 | // GIVEN an account on the devnet connector with accountId = sdk_account1 40 | 41 | // WHEN the balance of that account is requested with an auth token prefixed with 'Bearer ' 42 | try { 43 | await ILPClientNode.getBalance('sdk_account1', 'Bearer password') 44 | } catch (error) { 45 | // THEN an Error is thrown by IlpCredentials 46 | assert.equal( 47 | (error as IlpError).errorType, 48 | IlpErrorType.InvalidAccessToken, 49 | ) 50 | assert.equal(error.message, IlpError.invalidAccessToken.message) 51 | } 52 | }) 53 | 54 | it('Send Money - Node Shim', async function (): Promise { 55 | this.timeout(timeoutMs) 56 | // GIVEN an account on the connector with accountId = sdk_account1 57 | // AND an account on the connector with accountId = sdk_account2 58 | 59 | // WHEN a payment is sent from sdk_account1 to sdk_account2 for 10 units 60 | const request = new PaymentRequest({ 61 | amount: bigInt(10), 62 | destinationPaymentPointer: '$stage.xpring.money/sdk_account2', 63 | senderAccountId: 'sdk_account1', 64 | }) 65 | const message = await ILPClientNode.sendPayment(request, 'password') 66 | 67 | // THEN the originalAmount should be equal to the amount sent 68 | assert.equal(message.originalAmount.valueOf(), 10) 69 | // AND the amountSent should be equal to the originalAmount 70 | assert.equal(message.amountSent.valueOf(), 10) 71 | // AND the amountDelivered should be 10 72 | assert.equal(message.amountDelivered.valueOf(), 10) 73 | // AND the payment should be successful 74 | assert.equal(message.successfulPayment, true) 75 | }) 76 | 77 | it('Send Money - Bearer Token', async function (): Promise { 78 | this.timeout(timeoutMs) 79 | 80 | // GIVEN an account on the connector with accountId = sdk_account1 81 | // AND an account on the connector with accountId = sdk_account2 82 | 83 | // WHEN a payment is sent from sdk_account1 to sdk_account2 for 10 units 84 | try { 85 | const request = new PaymentRequest({ 86 | amount: bigInt(10), 87 | destinationPaymentPointer: '$spsp-test.xpring.io/sdk_account2', 88 | senderAccountId: 'sdk_account1', 89 | }) 90 | await ILPClientNode.sendPayment(request, 'Bearer password') 91 | } catch (error) { 92 | // THEN an Error is thrown by IlpCredentials 93 | assert.equal( 94 | (error as IlpError).errorType, 95 | IlpErrorType.InvalidAccessToken, 96 | ) 97 | assert.equal(error.message, IlpError.invalidAccessToken.message) 98 | } 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /test/PayID/fakes/fake-xrp-pay-id-client.ts: -------------------------------------------------------------------------------- 1 | import XrpPayIdClientInterface from '../../../src/PayID/xrp-pay-id-client-interface' 2 | import Result from '../../Common/Helpers/result' 3 | import { XrplNetwork } from 'xpring-common-js' 4 | 5 | /** 6 | * A fake XrpPayIdClient which can return faked values. 7 | */ 8 | export default class FakeXrpPayIdClient implements XrpPayIdClientInterface { 9 | /** 10 | * @param xrpAddressResult The object that will be returned or thrown from a call to `xrpAddressForPayID`. 11 | */ 12 | constructor( 13 | private readonly xrpAddressResult: Result, 14 | public readonly xrplNetwork: XrplNetwork = XrplNetwork.Test, 15 | ) {} 16 | 17 | // eslint-disable-next-line @typescript-eslint/require-await 18 | async xrpAddressForPayId(_payID: string): Promise { 19 | if (this.xrpAddressResult instanceof Error) { 20 | throw this.xrpAddressResult 21 | } 22 | 23 | return this.xrpAddressResult 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/PayID/pay-id-integration.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import PayIdError, { PayIdErrorType } from '../../src/PayID/pay-id-error' 3 | import PayIdClient from '../../src/PayID/pay-id-client' 4 | import XrpPayIdClient from '../../src/PayID/xrp-pay-id-client' 5 | import { XrplNetwork } from 'xpring-common-js' 6 | 7 | // A timeout for these tests. 8 | const timeoutMs = 60 * 1000 // 1 minute 9 | 10 | describe('PayID Integration Tests', function (): void { 11 | it('Resolve PayID to XRP - known PayID - mainnet', async function (): Promise { 12 | this.timeout(timeoutMs) 13 | 14 | // GIVEN a PayID that will resolve on Mainnet. 15 | const payIdClient = new XrpPayIdClient(XrplNetwork.Main) 16 | const payId = 'alice$dev.payid.xpring.money' 17 | 18 | // WHEN it is resolved to an XRP address 19 | const xrpAddress = await payIdClient.xrpAddressForPayId(payId) 20 | 21 | // THEN the address is the expected value. 22 | assert.equal(xrpAddress, 'X7zmKiqEhMznSXgj9cirEnD5sWo3iZSbeFRexSFN1xZ8Ktn') 23 | }) 24 | 25 | it('Resolve PayID to XRP - known PayID - testnet', async function (): Promise { 26 | this.timeout(timeoutMs) 27 | 28 | // GIVEN a PayID that will resolve on Testnet. 29 | const payIdClient = new XrpPayIdClient(XrplNetwork.Test) 30 | const payId = 'alice$dev.payid.xpring.money' 31 | 32 | // WHEN it is resolved to an XRP address on testnet 33 | const xrpAddress = await payIdClient.xrpAddressForPayId(payId) 34 | 35 | // THEN the address is the expected value. 36 | assert.equal(xrpAddress, 'TVacixsWrqyWCr98eTYP7FSzE9NwupESR4TrnijN7fccNiS') 37 | }) 38 | 39 | it('Resolve PayID to XRP - unknown PayID - devnet', function (done) { 40 | this.timeout(timeoutMs) 41 | 42 | // GIVEN a PayID that will not resolve on Devnet. 43 | const payId = 'does-not-exist$dev.payid.xpring.money' 44 | const network = XrplNetwork.Dev 45 | const payIdClient = new XrpPayIdClient(network) 46 | 47 | // WHEN it is resolved to an unmapped value. 48 | payIdClient.xrpAddressForPayId(payId).catch((error) => { 49 | // THEN an unexpected response is thrown with the details of the error. 50 | assert.equal( 51 | (error as PayIdError).errorType, 52 | PayIdErrorType.MappingNotFound, 53 | ) 54 | 55 | const { message } = error 56 | assert.include(message, payId) 57 | assert.include(message, network) 58 | 59 | done() 60 | }) 61 | }) 62 | 63 | it('Resolve PayID to BTC - known PayID - testnet', async function (): Promise { 64 | this.timeout(timeoutMs) 65 | 66 | // GIVEN a PayID that will resolve on BTC testnet. 67 | const payIdClient = new PayIdClient() 68 | const payId = 'alice$dev.payid.xpring.money' 69 | const network = 'btc-testnet' 70 | 71 | // WHEN it is resolved to an XRP address 72 | const btcAddress = await payIdClient.cryptoAddressForPayId(payId, network) 73 | 74 | // THEN the address is the expected value. 75 | assert.deepEqual(btcAddress, { 76 | address: '2NF9H32iwQcVcoAiiBmAtjpGmQfsmU5L6SR', 77 | }) 78 | }) 79 | 80 | it('resolves all addresses', async function (): Promise { 81 | // GIVEN a PayID with multiple addresses. 82 | const payId = 'alice$dev.payid.xpring.money' 83 | const payIdClient = new PayIdClient() 84 | 85 | // WHEN the PayID is resolved to a set of addresses. 86 | const addresses = await payIdClient.allAddressesForPayId(payId) 87 | 88 | // THEN multiple results are returned. 89 | assert(addresses.length > 1) 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /test/PayID/pay-id-utils.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | 3 | import { PayIdUtils } from '../../src/index' 4 | import 'mocha' 5 | 6 | describe('PayIdUtils', function (): void { 7 | it('parse PayID - valid', function (): void { 8 | // GIVEN a PayID with a host and a path. 9 | const host = 'xpring.money' 10 | const path = 'georgewashington' 11 | const rawPayID = `${path}$${host}` 12 | 13 | // WHEN it is parsed to components. 14 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 15 | 16 | // THEN the host and path are set correctly. 17 | assert.equal(payIDComponents?.host, host) 18 | assert.equal(payIDComponents?.path, `/${path}`) 19 | }) 20 | 21 | it('parse PayID - valid multiple dollar signs', function (): void { 22 | // GIVEN a PayID with more than one '$'. 23 | const host = 'xpring.money' 24 | const path = 'george$$$washington$' 25 | const rawPayID = `${path}$${host}` 26 | 27 | // WHEN it is parsed to components. 28 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 29 | 30 | // THEN the host and path are set correctly. 31 | assert.equal(payIDComponents?.host, host) 32 | assert.equal(payIDComponents?.path, `/${path}`) 33 | }) 34 | 35 | it('parse PayID - invalid multiple dollar signs (ends with $)', function (): void { 36 | // GIVEN a PayID in which the host ends with a $. 37 | const host = 'xpring.money$' 38 | const path = 'george$$$washington$' 39 | const rawPayID = `${path}$${host}` 40 | 41 | // WHEN it is parsed to components. 42 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 43 | 44 | // THEN the PayID failed to parse. 45 | assert.isUndefined(payIDComponents) 46 | }) 47 | 48 | it('parse PayID - no dollar signs', function (): void { 49 | // GIVEN a PayID that contains no dollar signs 50 | const rawPayID = `georgewashington@xpring.money` 51 | 52 | // WHEN it is parsed to components. 53 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 54 | 55 | // THEN the PayID failed to parse. 56 | assert.isUndefined(payIDComponents) 57 | }) 58 | 59 | it('parse PayID - empty host', function (): void { 60 | // GIVEN a PayID with an empty host. 61 | const host = '' 62 | const path = 'georgewashington' 63 | const rawPayID = `${path}$${host}` 64 | 65 | // WHEN it is parsed to components. 66 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 67 | 68 | // THEN the PayID failed to parse. 69 | assert.isUndefined(payIDComponents) 70 | }) 71 | 72 | it('parse PayID - empty path', function (): void { 73 | // GIVEN a PayID with an empty path. 74 | const host = 'xpring.money' 75 | const path = '' 76 | const rawPayID = `${path}$${host}` 77 | 78 | // WHEN it is parsed to components. 79 | const payIDComponents = PayIdUtils.parsePayId(rawPayID) 80 | 81 | // THEN the PayID failed to parse. 82 | assert.isUndefined(payIDComponents) 83 | }) 84 | 85 | it('parse PayID - non-ascii characters', function (): void { 86 | // GIVEN a PayID with non-ascii characters. 87 | const rawPayID = 'ZA̡͊͠͝LGΌIS̯͈͕̹̘̱ͮ$TO͇̹̺ͅƝ̴ȳ̳TH̘Ë͖́̉ ͠P̯͍̭O̚N̐Y̡' 88 | 89 | // WHEN it is parsed to components THEN the result is undefined 90 | assert.isUndefined(PayIdUtils.parsePayId(rawPayID)) 91 | }) 92 | }) 93 | -------------------------------------------------------------------------------- /test/XRP/fakes/fake-core-xrpl-client.ts: -------------------------------------------------------------------------------- 1 | import CoreXrplClientInterface from '../../../src/XRP/core-xrpl-client-interface' 2 | import { Wallet } from '../../../src/index' 3 | import Result from '../../Common/Helpers/result' 4 | import { XrplNetwork } from 'xpring-common-js' 5 | import RawTransactionStatus from '../../../src/XRP/shared/raw-transaction-status' 6 | import TransactionResult from '../../../src/XRP/shared/transaction-result' 7 | 8 | class FakeCoreXrplClient implements CoreXrplClientInterface { 9 | public constructor( 10 | public awaitFinalTransactionStatusValue: Result<{ 11 | rawTransactionStatus: RawTransactionStatus 12 | lastLedgerPassed: boolean 13 | }>, 14 | public awaitFinalTransactionResultValue: Result, 15 | public network: XrplNetwork = XrplNetwork.Test, 16 | ) {} 17 | 18 | // eslint-disable-next-line @typescript-eslint/require-await 19 | public async waitForFinalTransactionOutcome( 20 | _transactionHash: string, 21 | _sender: Wallet, 22 | ): Promise<{ 23 | rawTransactionStatus: RawTransactionStatus 24 | lastLedgerPassed: boolean 25 | }> { 26 | return FakeCoreXrplClient.returnOrThrow( 27 | this.awaitFinalTransactionStatusValue, 28 | ) 29 | } 30 | 31 | public async getFinalTransactionResultAsync( 32 | _transactionHash: string, 33 | _sender: Wallet, 34 | ): Promise { 35 | return FakeCoreXrplClient.returnOrThrow( 36 | this.awaitFinalTransactionResultValue, 37 | ) 38 | } 39 | 40 | // eslint-disable-next-line @typescript-eslint/require-await 41 | private static async returnOrThrow(value: Result): Promise { 42 | if (value instanceof Error) { 43 | throw value 44 | } 45 | return value 46 | } 47 | } 48 | 49 | export default FakeCoreXrplClient 50 | -------------------------------------------------------------------------------- /test/XRP/fakes/fake-xrp-client.ts: -------------------------------------------------------------------------------- 1 | import { BigInteger } from 'big-integer' 2 | import XrpClientDecorator from '../../../src/XRP/xrp-client-decorator' 3 | import TransactionStatus from '../../../src/XRP/shared/transaction-status' 4 | import { Wallet } from '../../../src/index' 5 | import XrpTransaction from '../../../src/XRP/protobuf-wrappers/xrp-transaction' 6 | import Result from '../../Common/Helpers/result' 7 | import { XrplNetwork } from 'xpring-common-js' 8 | import SendXrpDetails from '../../../src/XRP/shared/send-xrp-details' 9 | import TransactionResult from '../../../src/XRP/shared/transaction-result' 10 | 11 | class FakeXrpClient implements XrpClientDecorator { 12 | public constructor( 13 | public getBalanceValue: Result, 14 | public getPaymentStatusValue: Result, 15 | public transactionResult: Result, 16 | public accountExistsValue: Result, 17 | public paymentHistoryValue: Result>, 18 | public getPaymentValue: Result, 19 | public enableDepositAuthValue: Result, 20 | public authorizeSendingAccountValue: Result, 21 | public unauthorizeSendingAccountValue: Result, 22 | // TODO: remove `transactionHash` from constructor once `XpringClient` is deprecated and tests are removed. 23 | public transactionHash: Result = 'deadbeef', 24 | public readonly network: XrplNetwork = XrplNetwork.Test, 25 | ) {} 26 | 27 | async getBalance(_address: string): Promise { 28 | return FakeXrpClient.returnOrThrow(this.getBalanceValue) 29 | } 30 | 31 | async getPaymentStatus(_transactionHash: string): Promise { 32 | return FakeXrpClient.returnOrThrow(this.getPaymentStatusValue) 33 | } 34 | 35 | // TODO: remove `send` and `sendWithDetails` once `XpringClient` has been deprecated and tests removed. 36 | async send( 37 | _amount: BigInteger | number | string, 38 | _destination: string, 39 | _sender: Wallet, 40 | ): Promise { 41 | return FakeXrpClient.returnOrThrow(this.transactionHash) 42 | } 43 | 44 | async sendWithDetails(_sendXrpDetails: SendXrpDetails): Promise { 45 | return FakeXrpClient.returnOrThrow(this.transactionHash) 46 | } 47 | 48 | async sendXrp( 49 | _amount: BigInteger | number | string, 50 | _destination: string, 51 | _sender: Wallet, 52 | ): Promise { 53 | return FakeXrpClient.returnOrThrow(this.transactionResult) 54 | } 55 | 56 | async sendXrpWithDetails( 57 | _sendXrpDetails: SendXrpDetails, 58 | ): Promise { 59 | return FakeXrpClient.returnOrThrow(this.transactionResult) 60 | } 61 | 62 | async accountExists(_address: string): Promise { 63 | return FakeXrpClient.returnOrThrow(this.accountExistsValue) 64 | } 65 | 66 | async paymentHistory(_address: string): Promise> { 67 | return FakeXrpClient.returnOrThrow(this.paymentHistoryValue) 68 | } 69 | 70 | async getPayment(_transactionHash: string): Promise { 71 | return FakeXrpClient.returnOrThrow(this.getPaymentValue) 72 | } 73 | 74 | async enableDepositAuth(_wallet: Wallet): Promise { 75 | return FakeXrpClient.returnOrThrow(this.enableDepositAuthValue) 76 | } 77 | 78 | async authorizeSendingAccount( 79 | _xAddressToAuthorize: string, 80 | _wallet: Wallet, 81 | ): Promise { 82 | return FakeXrpClient.returnOrThrow(this.authorizeSendingAccountValue) 83 | } 84 | 85 | async unauthorizeSendingAccount( 86 | _xAddressToUnauthorize: string, 87 | _wallet: Wallet, 88 | ): Promise { 89 | return FakeXrpClient.returnOrThrow(this.unauthorizeSendingAccountValue) 90 | } 91 | 92 | // eslint-disable-next-line @typescript-eslint/require-await 93 | private static async returnOrThrow(value: Result): Promise { 94 | if (value instanceof Error) { 95 | throw value 96 | } 97 | return value 98 | } 99 | } 100 | 101 | export default FakeXrpClient 102 | -------------------------------------------------------------------------------- /test/XRP/protos-models-flags-utils/raw-transaction-status.test.ts: -------------------------------------------------------------------------------- 1 | import 'mocha' 2 | import { assert } from 'chai' 3 | 4 | import { Flags } from '../../../src/XRP/Generated/web/org/xrpl/rpc/v1/common_pb' 5 | import { GetTransactionResponse } from '../../../src/XRP/Generated/web/org/xrpl/rpc/v1/get_transaction_pb' 6 | import { 7 | Transaction, 8 | Payment, 9 | } from '../../../src/XRP/Generated/web/org/xrpl/rpc/v1/transaction_pb' 10 | import RawTransactionStatus from '../../../src/XRP/shared/raw-transaction-status' 11 | import PaymentFlags from '../../../src/XRP/shared/payment-flags' 12 | import { 13 | Meta, 14 | TransactionResult, 15 | } from '../../../src/XRP/Generated/node/org/xrpl/rpc/v1/meta_pb' 16 | 17 | describe('raw transaction status', function (): void { 18 | it('isFullPayment - non payment', function (): void { 19 | // GIVEN a getTxResponse which is not a payment. 20 | const transaction = new Transaction() 21 | transaction.clearPayment() 22 | 23 | const transactionResult = new TransactionResult() 24 | transactionResult.setResult('tesSUCCESS') 25 | const meta = new Meta() 26 | meta.setTransactionResult(transactionResult) 27 | const getTxResponse = new GetTransactionResponse() 28 | getTxResponse.setTransaction(transaction) 29 | getTxResponse.setMeta(meta) 30 | 31 | // WHEN the raw transaction status is wrapped in a RawTransactionStatus object. 32 | const rawTransactionStatus = RawTransactionStatus.fromGetTransactionResponse( 33 | getTxResponse, 34 | ) 35 | 36 | // THEN the raw transaction status reports it is not a full payment. 37 | assert.isFalse(rawTransactionStatus.isFullPayment) 38 | }) 39 | 40 | it('isFullPayment - partial payment', function (): void { 41 | // GIVEN a getTxResponse which is a payment with the partial payment flags set. 42 | const payment = new Payment() 43 | 44 | const flags = new Flags() 45 | flags.setValue(PaymentFlags.TF_PARTIAL_PAYMENT) 46 | 47 | const transactionResult = new TransactionResult() 48 | transactionResult.setResult('tesSUCCESS') 49 | const meta = new Meta() 50 | meta.setTransactionResult(transactionResult) 51 | const transaction = new Transaction() 52 | transaction.setPayment(payment) 53 | transaction.setFlags(flags) 54 | 55 | const getTxResponse = new GetTransactionResponse() 56 | getTxResponse.setTransaction(transaction) 57 | getTxResponse.setMeta(meta) 58 | 59 | // WHEN the raw transaction status is wrapped in a RawTransactionStatus object. 60 | const rawTransactionStatus = RawTransactionStatus.fromGetTransactionResponse( 61 | getTxResponse, 62 | ) 63 | 64 | // THEN the raw transaction status reports it is not a full payment. 65 | assert.isFalse(rawTransactionStatus.isFullPayment) 66 | }) 67 | 68 | it('isFullPayment - payment', function (): void { 69 | // GIVEN a getTxResponse which is a payment. 70 | const payment = new Payment() 71 | 72 | const transaction = new Transaction() 73 | transaction.setPayment(payment) 74 | 75 | const transactionResult = new TransactionResult() 76 | transactionResult.setResult('tesSUCCESS') 77 | const meta = new Meta() 78 | meta.setTransactionResult(transactionResult) 79 | const getTxResponse = new GetTransactionResponse() 80 | getTxResponse.setTransaction(transaction) 81 | getTxResponse.setMeta(meta) 82 | 83 | // WHEN the raw transaction status is wrapped in a RawTransactionStatus object. 84 | const rawTransactionStatus = RawTransactionStatus.fromGetTransactionResponse( 85 | getTxResponse, 86 | ) 87 | 88 | // THEN the raw transaction status reports it is a full payment. 89 | assert.isTrue(rawTransactionStatus.isFullPayment) 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /test/XRP/protos-models-flags-utils/rippled-flags.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-bitwise -- Explicitly testing bitwise flags in this file */ 2 | import { assert } from 'chai' 3 | 4 | import PaymentFlags from '../../../src/XRP/shared/payment-flags' 5 | import 'mocha' 6 | 7 | describe('rippled flags', function (): void { 8 | it('check - flag present', function (): void { 9 | // GIVEN a set of flags that contains the tfPartialPayment flag. 10 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 and 4 are arbitrary chosen 11 | const flags = PaymentFlags.TF_PARTIAL_PAYMENT | 1 | 4 12 | 13 | // WHEN the presence of tfPartialPayment is checked THEN the flag is reported as present. 14 | assert.isTrue( 15 | PaymentFlags.checkFlag(PaymentFlags.TF_PARTIAL_PAYMENT, flags), 16 | ) 17 | }) 18 | 19 | it('check - flag notpresent', function (): void { 20 | // GIVEN a set of flags that does not contain the tfPartialPayment flag. 21 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 and 4 are arbitrary chosen 22 | const flags = 1 | 4 23 | 24 | // WHEN the presence of tfPartialPayment is checked THEN the flag is reported as not present. 25 | assert.isFalse( 26 | PaymentFlags.checkFlag(PaymentFlags.TF_PARTIAL_PAYMENT, flags), 27 | ) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/XRP/protos-models-flags-utils/trust-line.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | 3 | import TrustLine from '../../../src/XRP/shared/trustline' 4 | import 'mocha' 5 | import { TrustLineResponse } from '../../../src/XRP/shared/rippled-web-socket-schema' 6 | 7 | describe('TrustLine Conversion Tests', function (): void { 8 | it('TrustLine from Object - all fields present', function (): void { 9 | // GIVEN a raw object representing a trust line on the XRPL 10 | const testTrustLineAllFields: TrustLineResponse = { 11 | account: 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z', 12 | balance: '0', 13 | currency: 'USD', 14 | limit: '0', 15 | limit_peer: '10', 16 | quality_in: 0, 17 | quality_out: 0, 18 | no_ripple: true, 19 | no_ripple_peer: true, 20 | authorized: true, 21 | peer_authorized: true, 22 | freeze: true, 23 | freeze_peer: true, 24 | } 25 | 26 | // WHEN a TrustLine object is constructed from it 27 | const trustLine = new TrustLine(testTrustLineAllFields) 28 | 29 | // THEN the result is as expected. 30 | assert.equal(trustLine.account, 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z') 31 | assert.equal(trustLine.balance, '0') 32 | assert.equal(trustLine.currency, 'USD') 33 | assert.equal(trustLine.limit, '0') 34 | assert.equal(trustLine.limitPeer, '10') 35 | assert.equal(trustLine.qualityIn, 0) 36 | assert.equal(trustLine.qualityOut, 0) 37 | assert.equal(trustLine.noRipple, true) 38 | assert.equal(trustLine.noRipplePeer, true) 39 | assert.equal(trustLine.authorized, true) 40 | assert.equal(trustLine.peerAuthorized, true) 41 | assert.equal(trustLine.freeze, true) 42 | assert.equal(trustLine.freezePeer, true) 43 | }) 44 | 45 | it('TrustLine from Object - missing some optional booleans', function (): void { 46 | // GIVEN a raw object representing a trust line on the XRPL with some missing optional fields 47 | const testTrustLineMissingOptionals: TrustLineResponse = { 48 | account: 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z', 49 | balance: '0', 50 | currency: 'USD', 51 | limit: '0', 52 | limit_peer: '10', 53 | quality_in: 0, 54 | quality_out: 0, 55 | } 56 | 57 | // WHEN a TrustLine object is constructed from it 58 | const trustLine = new TrustLine(testTrustLineMissingOptionals) 59 | 60 | // THEN the result is as expected 61 | assert.equal(trustLine.account, 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z') 62 | assert.equal(trustLine.balance, '0') 63 | assert.equal(trustLine.currency, 'USD') 64 | assert.equal(trustLine.limit, '0') 65 | assert.equal(trustLine.limitPeer, '10') 66 | assert.equal(trustLine.qualityIn, 0) 67 | assert.equal(trustLine.qualityOut, 0) 68 | assert.equal(trustLine.noRipple, false) 69 | assert.equal(trustLine.noRipplePeer, false) 70 | assert.equal(trustLine.authorized, false) 71 | assert.equal(trustLine.peerAuthorized, false) 72 | assert.equal(trustLine.freeze, false) 73 | assert.equal(trustLine.freezePeer, false) 74 | }) 75 | 76 | it('TrustLine from Object - optional booleans presented as false', function (): void { 77 | // GIVEN a raw object representing a trust line on the XRPL with optional falsey booleans explicitly set to false 78 | const testTrustLineFalseOptionals: TrustLineResponse = { 79 | account: 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z', 80 | balance: '0', 81 | currency: 'USD', 82 | limit: '0', 83 | limit_peer: '10', 84 | quality_in: 0, 85 | quality_out: 0, 86 | no_ripple: false, 87 | authorized: true, 88 | peer_authorized: false, 89 | freeze: true, 90 | } 91 | 92 | // WHEN a TrustLine object is constructed from it 93 | const trustLine = new TrustLine(testTrustLineFalseOptionals) 94 | 95 | // THEN the result is as expected 96 | assert.equal(trustLine.account, 'r3vi7mWxru9rJCxETCyA1CHvzL96eZWx5z') 97 | assert.equal(trustLine.balance, '0') 98 | assert.equal(trustLine.currency, 'USD') 99 | assert.equal(trustLine.limit, '0') 100 | assert.equal(trustLine.limitPeer, '10') 101 | assert.equal(trustLine.qualityIn, 0) 102 | assert.equal(trustLine.qualityOut, 0) 103 | assert.equal(trustLine.noRipple, false) 104 | assert.equal(trustLine.noRipplePeer, false) 105 | assert.equal(trustLine.authorized, true) 106 | assert.equal(trustLine.peerAuthorized, false) 107 | assert.equal(trustLine.freeze, true) 108 | assert.equal(trustLine.freezePeer, false) 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /test/Xpring/xpring-client-integration.test.ts: -------------------------------------------------------------------------------- 1 | import { assert } from 'chai' 2 | import { XrplNetwork } from 'xpring-common-js' 3 | 4 | import XrpPayIdClient from '../../src/PayID/xrp-pay-id-client' 5 | import XpringClient from '../../src/Xpring/xpring-client' 6 | import XrpClient from '../../src/XRP/xrp-client' 7 | import XRPTestUtils, { 8 | iForgotToPickUpCarlMemo, 9 | noDataMemo, 10 | noFormatMemo, 11 | noTypeMemo, 12 | } from '../XRP/helpers/xrp-test-utils' 13 | 14 | // A timeout for these tests. 15 | // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 minute in milliseconds 16 | const timeoutMs = 60 * 1000 17 | 18 | // The network to conduct tests on. 19 | const network = XrplNetwork.Test 20 | 21 | // A PayIdClient under test. 22 | const payIdClient = new XrpPayIdClient(network) 23 | 24 | // An XrpClient under test. 25 | const rippledUrl = 'test.xrp.xpring.io:50051' 26 | const xrpClient = new XrpClient(rippledUrl, XrplNetwork.Test) 27 | 28 | // A XpringClient under test. 29 | const xpringClient = new XpringClient(payIdClient, xrpClient) 30 | 31 | describe('Xpring Integration Tests', function (): void { 32 | // A Wallet with some balance on Testnet. 33 | let wallet 34 | before(async function () { 35 | wallet = await XRPTestUtils.randomWalletFromFaucet() 36 | }) 37 | 38 | it('Send XRP TestNet', async function (): Promise { 39 | this.timeout(timeoutMs) 40 | 41 | // GIVEN an amount and a PayID that will resolve. 42 | const amount = 10 43 | const payId = 'alice$dev.payid.xpring.money' 44 | 45 | // WHEN XRP is sent to the PayID. 46 | const transactionHash = await xpringClient.send(amount, payId, wallet) 47 | 48 | // THEN a transaction hash is returned. 49 | assert.exists(transactionHash) 50 | }) 51 | 52 | it('Send XRP TestNet with memos', async function (): Promise { 53 | this.timeout(timeoutMs) 54 | 55 | // GIVEN an amount, a PayID that will resolve, and some memos. 56 | const amount = 10 57 | const payID = 'alice$dev.payid.xpring.money' 58 | const memoList = [ 59 | iForgotToPickUpCarlMemo, 60 | noDataMemo, 61 | noFormatMemo, 62 | noTypeMemo, 63 | ] 64 | 65 | // WHEN XRP is sent to the PayID, including a memo. 66 | const transactionHash = await xpringClient.sendWithDetails({ 67 | amount, 68 | destination: payID, 69 | sender: wallet, 70 | memoList, 71 | }) 72 | 73 | // THEN a transaction hash is returned and the memos are present. 74 | assert.exists(transactionHash) 75 | 76 | const transaction = await xrpClient.getPayment(transactionHash) 77 | 78 | assert.deepEqual(transaction?.memos, [ 79 | iForgotToPickUpCarlMemo, 80 | noDataMemo, 81 | noFormatMemo, 82 | noTypeMemo, 83 | ]) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | // We need this file to run ESLint on our tests 2 | // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md#configuration 3 | { 4 | // extend your base config so you don't have to redefine your compilerOptions 5 | "extends": "./tsconfig.json", 6 | "include": ["src/**/*.ts", "test/**/*.ts", "./.eslintrc.js", "./webpack.config.js"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Basic Options 4 | "target": "ES2017", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "sourceMap": true, 8 | 9 | // Directories 10 | "outDir": "build", 11 | 12 | // Strict Type-Checking Options 13 | "strict": true, 14 | "noImplicitAny": false, 15 | 16 | // Additional Checks 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "noImplicitReturns": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Module Resolution Options 23 | "moduleResolution": "node", 24 | 25 | // Advanced Options 26 | "allowJs": true, 27 | "forceConsistentCasingInFileNames": true, 28 | "esModuleInterop": true 29 | }, 30 | "include": [ 31 | "src/**/*" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack considers .web.ts and .ts to be different extensions 2 | // We use both for web vs node implementations of some clients. 3 | const moduleFileExtensions = ['js', 'web.ts', 'ts'] 4 | 5 | module.exports = { 6 | target: 'web', 7 | mode: 'production', 8 | entry: './src/index.ts', 9 | devtool: 'source-map', 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts$/, 14 | exclude: /(node_modules)/, 15 | use: { 16 | loader: 'babel-loader', 17 | options: { 18 | presets: ['@babel/typescript', '@babel/preset-env'], 19 | plugins: [ 20 | '@babel/plugin-transform-runtime', 21 | '@babel/plugin-proposal-class-properties', 22 | ], 23 | }, 24 | }, 25 | }, 26 | ], 27 | }, 28 | resolve: { 29 | extensions: moduleFileExtensions.map((ext) => `.${ext}`), 30 | // Some libraries import Node modules but don't use them in the browser. 31 | // Tell webpack to provide empty mocks for them so importing them works. 32 | fallback: { 33 | module: false, 34 | dns: 'mock', 35 | fs: false, 36 | http2: false, 37 | net: false, 38 | tls: false, 39 | child_process: false, 40 | // Webpack no longer auto-polyfills for node core modules. 41 | // These are the polyfills for the necessary modules. 42 | assert: 'assert', 43 | buffer: 'buffer', 44 | crypto: 'crypto-browserify', 45 | os: 'os-browserify/browser', 46 | stream: 'stream-browserify', 47 | url: 'url', 48 | util: 'util', 49 | zlib: 'browserify-zlib', 50 | }, 51 | }, 52 | output: { 53 | filename: 'index.js', 54 | library: 'XpringJS', 55 | libraryTarget: 'umd', 56 | globalObject: "(typeof self !== 'undefined' ? self : this)", 57 | }, 58 | } 59 | --------------------------------------------------------------------------------