├── LICENSE.txt ├── README.md ├── authentication ├── oauth-authorization-code │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── authorization-code-flow.js │ ├── cert.pem │ ├── index.js │ ├── key.pem │ ├── package-lock.json │ └── package.json ├── oauth-client-credentials │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── client-credentials-flow.js │ ├── index.js │ ├── package-lock.json │ └── package.json └── personal-access-token │ ├── .env.example │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── personal-access-token.js └── transactions └── user-to-user-transaction ├── .env.example ├── .gitignore ├── README.md ├── assets └── transaction-scopes.png ├── index.js ├── package-lock.json ├── package.json └── user-to-user-transaction.js /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Uphold 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API usage samples 2 | 3 | This repository contains [minimal, self-contained, executable example projects](http://www.sscce.org/) 4 | which demonstrate how to perform specific operations 5 | using [Uphold's REST API](https://uphold.com/en/developer/api/documentation/). 6 | 7 | > **Please note** that the examples contained here are optimized for clarity and simplicity. 8 | > They are not meant to illustrate the coding style or structure that we'd recommend for production applications, 9 | > but rather provide an entry point to start experimenting with Uphold's API. 10 | 11 | ## Usage 12 | 13 | To try out each example, navigate to the corresponding folder and follow the instructions in the README. 14 | 15 | The currently available examples are: 16 | 17 | - Authentication 18 | - [OAuth: Client credentials flow](authentication/oauth-client-credentials/) 19 | - [OAuth: Authorization code flow](authentication/oauth-authorization-code/) 20 | - [Personal Access Token (PAT)](authentication/personal-access-token/) 21 | - Transactions 22 | - [User-to-user transaction](transactions/user-to-user-transaction/) 23 | 24 | ## Contributing 25 | 26 | Feel free to submit your own examples to this repository! Just make sure to follow the 27 | [MRE](https://stackoverflow.com/help/minimal-reproducible-example)/[SSCCE](http://www.sscce.org/) principles, 28 | and describe all steps to run the project in a README file similar to those of the current examples. 29 | 30 | The contents of this repository are licensed under the [MIT license](LICENSE.txt). 31 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/.env.example: -------------------------------------------------------------------------------- 1 | # Base Url endpoint 2 | BASE_URL = 'https://api-sandbox.uphold.com' 3 | 4 | CLIENT_ID = '' 5 | CLIENT_SECRET = '' 6 | SERVER_PORT = 3000 7 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/README.md: -------------------------------------------------------------------------------- 1 | # Authorization code OAuth flow 2 | 3 | This sample project demonstrates how a registered app can request authorization from Uphold users to perform actions on their behalf, 4 | by using the [authorization code OAuth flow](https://oauth.net/2/grant-types/authorization-code/). 5 | For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). 6 | 7 | ## Summary 8 | 9 | This flow is **recommended for web applications** that wish to retrieve information about a user's Uphold account, 10 | or take actions on their behalf. 11 | 12 | This process, sometimes called "3-legged OAuth", requires three steps, each initiated by one of the three actors: 13 | 14 | 1. The **user** navigates to Uphold's website, following an authorization URL generated by the app, 15 | where they log in and authorize the app to access their Uphold account; 16 | 2. **Uphold** redirects the user back to the app's website, including a short-lived authorization code in the URL; 17 | 3. The **application**'s server submits this code to Uphold's API, obtaining a long-lived access token in response. 18 | (Since this final step occurs in server-to-server communication, the actual code is never exposed to the browser.) 19 | 20 | This example sets up a local server that can be used to perform the OAuth web application flow cycle as described above. 21 | 22 | ## Requirements 23 | 24 | To run this example, you must have: 25 | 26 | - Node.js v13.14.0 or later 27 | - An account at 28 | 29 | ## Setup 30 | 31 | - Run `npm install` (or `yarn install`) 32 | - [Create an app on Uphold Sandbox](https://sandbox.uphold.com/dashboard/profile/applications/developer/new) 33 | with the redirect URI field set to `https://localhost:3000/callback` 34 | (you may use a different port number, if you prefer). 35 | Note that this demo expects at least the `user:read` scope to be activated. 36 | - Create a `.env` file based on the `.env.example` file, and populate it with the required data. 37 | Make sure to also update the `SERVER_PORT` if you changed it in the previous step. 38 | 39 | ## Run 40 | 41 | - Run `node index.js` 42 | - Open the URL printed in the command line. 43 | - **Attention:** Since the certificate used in this demo is self-signed, not all browsers will allow navigating to the page. 44 | You can use Firefox or Safari, which will display a warning but allow you to proceed regardless. 45 | Alternatively, you can navigate to `chrome://flags/#allow-insecure-localhost` in Chromium-based browsers, 46 | to toggle support for self-signed localhost certificates. 47 | - Click the link in the page to navigate to Uphold's servers. 48 | - Accept the application's permission request. 49 | 50 | Once the authorization is complete and an access token is obtained, 51 | the local server will use it to make a test request to the Uphold API. 52 | The output will be printed in the command line. 53 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/authorization-code-flow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import axios from "axios"; 6 | import dotenv from "dotenv"; 7 | import path from "path"; 8 | 9 | // Dotenv configuration. 10 | dotenv.config({ path: path.resolve() + "/.env" }); 11 | 12 | // Authentication credentials. 13 | const auth = Buffer.from(process.env.CLIENT_ID + ":" + process.env.CLIENT_SECRET).toString("base64"); 14 | 15 | /** 16 | * Format API error response for printing in console. 17 | */ 18 | 19 | function formatError(error) { 20 | const responseStatus = `${error.response.status} (${error.response.statusText})`; 21 | 22 | console.log( 23 | `Request failed with HTTP status code ${responseStatus}`, 24 | JSON.stringify({ 25 | url: error.config.url, 26 | response: error.response.data 27 | }, null, 2) 28 | ); 29 | 30 | throw error; 31 | } 32 | 33 | /** 34 | * Exchange OAuth authorization code for an access token. 35 | */ 36 | 37 | export async function getAccessToken(code) { 38 | try { 39 | const response = await axios.request({ 40 | method: "POST", 41 | url: `${process.env.BASE_URL}/oauth2/token`, 42 | data: `code=${code}&grant_type=authorization_code`, 43 | headers: { 44 | Authorization: `Basic ${auth}`, 45 | "content-type": "application/x-www-form-urlencoded", 46 | }, 47 | }); 48 | 49 | return response.data; 50 | } catch (error) { 51 | formatError(error); 52 | } 53 | } 54 | 55 | /** 56 | * Get data about the currently authenticated user. 57 | */ 58 | 59 | export async function getUserInfo(accessToken) { 60 | try { 61 | const response = await axios.request({ 62 | method: "GET", 63 | url: `${process.env.BASE_URL}/v0/me`, 64 | headers: { 65 | Authorization: `Bearer ${accessToken}`, 66 | }, 67 | }); 68 | 69 | return response.data; 70 | } catch (error) { 71 | formatError(error); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEljCCAn4CCQCHHmqArzJctTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJw 3 | dDAeFw0yMDEwMjIxMzE1MTlaFw0yMTEwMjIxMzE1MTlaMA0xCzAJBgNVBAYTAnB0 4 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArsxVWb12Rmn7oocPIXe5 5 | SgiiRAqS4pZ2jnpxkN2rYp8sBBZCYAkRFNfIh+SUoOVPUhrM5JDZ9stmPHAba/XO 6 | oOhhq4paI082TqBiBqqjYtofqdKp3FjMLXcV+Y1jYnphZ6U+eZKsLUc0hWCK/TFa 7 | 6H5Y3EUip9t8xmNRkzo2xhd5gNTEsvFMx/1XzJVJ7T5kgjKF+SECEw3leQva1RPK 8 | g7+REF1McZlWqav8Y/qEQ3ZS1AIJLEHG0x1rSE9zf0PHiTYecPbYYLwMccHorKua 9 | owjQKt9MboFN5u+Ne99sTyLuemDBmBFEI87ecBBCDzQb/bOQoqKJDY4u1xjO5Crf 10 | tvVgZoGkwlXobJZ4r02Zfh338dXCIbTw9tXNV0sGtdiDMD3uPDq7+pU7mjWIcWjb 11 | 1hNVFxOO5Bna42r8q53QkfibKTVEZZaZmOu9vosOBGa4YsYUXvh0N1TqS7jNYv2H 12 | vTdKYZnJvEoFj4bXEpyA8Dk/roybij19l0d5w6SR34Aq1M63NxGwph4CiCJ2SMGU 13 | u+y048/XH64Bn1GjE19yyZ8JKi6tiENY6m9WS4BDGFiAOL5XkthcUmI5j8yaHncr 14 | iSIJmfEiwL3ZCiUzD3Ua3l5oSK+aG5hf7FU9rXNYnt6byerrZasZnGi8U4y4CzLA 15 | VEdfuklz3fBUVqz2fU6cFEkCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAW9j6giBn 16 | iiphY4GMuQlQr3mf/rrtqCDIV+SAkhi/IzKbk4x/5yXoLZ2r9FbcZmNigBjQqB16 17 | V0YN2lNYFiSF9Sx9Qv8XIFXYyPIKucHKekGASDk8oqmPtHQYBH9hOTRN8SRaT7aQ 18 | rV1pYqdkjjG4gtjauYTAXucgQjP7d4kj8jOadZCffN53/6ASPRkj/Q+vpUlj0dxd 19 | tjrEi1NxwbHahi59UggTg6ftLTgIMOHJYWMyTuR++B8m+UT6bFpPxB5enfcL+Qg9 20 | 4cTK0MtebyIIXmXv2L5S56/En+Kvlq3ynRFlqq9kdHK80kqjmPw6D2A+RHka1nDb 21 | uo61ZPxBznMk9s8SJix+lv3MvinOJCiJDjYhef0rZXSSUEmXa58IF7iZdV+SIlUp 22 | bEbEpCvVqBgc8XDoVcSp96rpZDSuSYfU7Xz9McyFbOtq+NkEtDevxE8r3WqIBh9x 23 | efss+CBkrdGyj5qyBTd8YyLKvY3fsPfS08BMN7cMZVw8wsICymAGUFHk5Do3RFxM 24 | tgD1VE26v0cluQwguYWZgRLR9lK1vREs7OfRb4RaXLczArOza5o4JTwmPByZ7owT 25 | PzK3H9ydn9oq8LoRoY+9s3IRgdRQSD/idf/QylsZ9Es4av9LO+6pvmO+Sr/rnIQb 26 | Gg6t7OPvwYNi62kS7eywQNbbfIB6wX0h/iw= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import dotenv from "dotenv"; 6 | import express from "express"; 7 | import fs from "fs"; 8 | import https from "https"; 9 | import path from "path"; 10 | import { randomBytes } from "crypto"; 11 | import { getUserInfo, getAccessToken } from "./authorization-code-flow.js"; 12 | 13 | // Dotenv configuration. 14 | dotenv.config({ path: path.resolve() + "/.env" }); 15 | 16 | // Server configuration. 17 | const app = express(); 18 | const port = process.env.SERVER_PORT || 3000; 19 | const state = randomBytes(8).toString('hex'); 20 | 21 | /** 22 | * Main page. 23 | */ 24 | 25 | app.get("/", async (req, res) => { 26 | // Compose the authorization URL. This assumes the `user:read` scope has been activated for this application. 27 | const authorizationUrl = 'https://sandbox.uphold.com/authorize/' 28 | + process.env.CLIENT_ID 29 | + '?scope=user:read' 30 | + '&state=' + state; 31 | 32 | res.send( 33 | `

Demo app server

34 |

Please authorize this app on Uphold's Sandbox.

` 35 | ); 36 | }); 37 | 38 | 39 | /** 40 | * Callback URL endpoint. 41 | */ 42 | 43 | app.get("/callback", async (req, res) => { 44 | try { 45 | // Show an error page if the code wasn't returned or the state doesn't match what we sent. 46 | if (!req.query.code || req.query.state !== state) { 47 | res.send(composeErrorPage(req.query, state)); 48 | } 49 | 50 | // Exchange the short-lived authorization code for a long-lived access token. 51 | const token = await getAccessToken(req.query.code); 52 | console.log(`Successfully exchanged authorization code ${req.query.code} for access token:`, token.access_token); 53 | 54 | // Test the new token by making an authenticated call to the API. 55 | const userData = await getUserInfo(token.access_token); 56 | console.log("Output from test API call:", userData); 57 | 58 | res.send( 59 | `

Success!

60 |

The OAuth authorization code has been successfully exchanged for an access token.

` 61 | ); 62 | } catch (error) { 63 | // Unexpected error. 64 | res.send(composeErrorPage(error)); 65 | return; 66 | } 67 | }); 68 | 69 | /** 70 | * Compose error web page. 71 | */ 72 | 73 | function composeErrorPage(data, state) { 74 | let content = "

Something went wrong.

"; 75 | 76 | if (data.state && data.state !== state) { 77 | content += `

The received state (${data.state}) does not match the expected value: ${state}.

`; 78 | } else if (data instanceof Error) { 79 | const errorData = { 80 | message: data.message, 81 | request: { 82 | url: data.config.url, 83 | method: data.config.method, 84 | data: data.config.data, 85 | headers: data.config.headers 86 | } 87 | }; 88 | content += "

Here are details of the error (see also the console log):

"; 89 | content += `
${JSON.stringify(errorData, null, 4)}
`; 90 | } else if (Object.values(data).length) { 91 | content += "

Here's what Uphold's servers returned:

"; 92 | content += `
${JSON.stringify(data, null, 4)}
`; 93 | } else { 94 | content += "

This page should be reached at the end of an OAuth authorization process.

"; 95 | content += "

Please confirm that you followed the steps in the README, and check the console log.

"; 96 | } 97 | 98 | return content; 99 | } 100 | 101 | /* 102 | * Check for the .env file. 103 | */ 104 | 105 | if (fs.existsSync('./.env') === false) { 106 | console.log("Missing .env file. Please follow the steps described in the README."); 107 | process.exit(); 108 | } 109 | 110 | /** 111 | * Run server. 112 | */ 113 | 114 | https 115 | .createServer({ 116 | key: fs.readFileSync("./key.pem"), 117 | cert: fs.readFileSync("./cert.pem"), 118 | passphrase: "test", 119 | }, app) 120 | .listen(port, () => { 121 | console.log(`Server running at https://localhost:${port}`); 122 | }); 123 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | 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 53 | idnWYlp+GmR0sckgYM6+f3R+PQ== 54 | -----END ENCRYPTED PRIVATE KEY----- 55 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-authorization-code-oauth-sample", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", 10 | "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "axios": { 22 | "version": "0.20.0", 23 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", 24 | "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", 25 | "requires": { 26 | "follow-redirects": "^1.10.0" 27 | } 28 | }, 29 | "body-parser": { 30 | "version": "1.19.0", 31 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", 32 | "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", 33 | "requires": { 34 | "bytes": "3.1.0", 35 | "content-type": "~1.0.4", 36 | "debug": "2.6.9", 37 | "depd": "~1.1.2", 38 | "http-errors": "1.7.2", 39 | "iconv-lite": "0.4.24", 40 | "on-finished": "~2.3.0", 41 | "qs": "6.7.0", 42 | "raw-body": "2.4.0", 43 | "type-is": "~1.6.17" 44 | } 45 | }, 46 | "bytes": { 47 | "version": "3.1.0", 48 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", 49 | "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" 50 | }, 51 | "content-disposition": { 52 | "version": "0.5.3", 53 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", 54 | "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", 55 | "requires": { 56 | "safe-buffer": "5.1.2" 57 | } 58 | }, 59 | "content-type": { 60 | "version": "1.0.4", 61 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 62 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 63 | }, 64 | "cookie": { 65 | "version": "0.4.0", 66 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", 67 | "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" 68 | }, 69 | "cookie-signature": { 70 | "version": "1.0.6", 71 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 72 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 73 | }, 74 | "debug": { 75 | "version": "2.6.9", 76 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 77 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 78 | "requires": { 79 | "ms": "2.0.0" 80 | } 81 | }, 82 | "depd": { 83 | "version": "1.1.2", 84 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 85 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 86 | }, 87 | "destroy": { 88 | "version": "1.0.4", 89 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 90 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 91 | }, 92 | "dotenv": { 93 | "version": "8.2.0", 94 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 95 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 96 | }, 97 | "ee-first": { 98 | "version": "1.1.1", 99 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 100 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 101 | }, 102 | "encodeurl": { 103 | "version": "1.0.2", 104 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 105 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 106 | }, 107 | "escape-html": { 108 | "version": "1.0.3", 109 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 110 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 111 | }, 112 | "etag": { 113 | "version": "1.8.1", 114 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 115 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 116 | }, 117 | "express": { 118 | "version": "4.17.1", 119 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", 120 | "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", 121 | "requires": { 122 | "accepts": "~1.3.7", 123 | "array-flatten": "1.1.1", 124 | "body-parser": "1.19.0", 125 | "content-disposition": "0.5.3", 126 | "content-type": "~1.0.4", 127 | "cookie": "0.4.0", 128 | "cookie-signature": "1.0.6", 129 | "debug": "2.6.9", 130 | "depd": "~1.1.2", 131 | "encodeurl": "~1.0.2", 132 | "escape-html": "~1.0.3", 133 | "etag": "~1.8.1", 134 | "finalhandler": "~1.1.2", 135 | "fresh": "0.5.2", 136 | "merge-descriptors": "1.0.1", 137 | "methods": "~1.1.2", 138 | "on-finished": "~2.3.0", 139 | "parseurl": "~1.3.3", 140 | "path-to-regexp": "0.1.7", 141 | "proxy-addr": "~2.0.5", 142 | "qs": "6.7.0", 143 | "range-parser": "~1.2.1", 144 | "safe-buffer": "5.1.2", 145 | "send": "0.17.1", 146 | "serve-static": "1.14.1", 147 | "setprototypeof": "1.1.1", 148 | "statuses": "~1.5.0", 149 | "type-is": "~1.6.18", 150 | "utils-merge": "1.0.1", 151 | "vary": "~1.1.2" 152 | } 153 | }, 154 | "finalhandler": { 155 | "version": "1.1.2", 156 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 157 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 158 | "requires": { 159 | "debug": "2.6.9", 160 | "encodeurl": "~1.0.2", 161 | "escape-html": "~1.0.3", 162 | "on-finished": "~2.3.0", 163 | "parseurl": "~1.3.3", 164 | "statuses": "~1.5.0", 165 | "unpipe": "~1.0.0" 166 | } 167 | }, 168 | "follow-redirects": { 169 | "version": "1.13.0", 170 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 171 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" 172 | }, 173 | "forwarded": { 174 | "version": "0.1.2", 175 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 176 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 177 | }, 178 | "fresh": { 179 | "version": "0.5.2", 180 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 181 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 182 | }, 183 | "http-errors": { 184 | "version": "1.7.2", 185 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", 186 | "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", 187 | "requires": { 188 | "depd": "~1.1.2", 189 | "inherits": "2.0.3", 190 | "setprototypeof": "1.1.1", 191 | "statuses": ">= 1.5.0 < 2", 192 | "toidentifier": "1.0.0" 193 | } 194 | }, 195 | "iconv-lite": { 196 | "version": "0.4.24", 197 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 198 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 199 | "requires": { 200 | "safer-buffer": ">= 2.1.2 < 3" 201 | } 202 | }, 203 | "inherits": { 204 | "version": "2.0.3", 205 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 206 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 207 | }, 208 | "ipaddr.js": { 209 | "version": "1.9.1", 210 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 211 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 212 | }, 213 | "media-typer": { 214 | "version": "0.3.0", 215 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 216 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 217 | }, 218 | "merge-descriptors": { 219 | "version": "1.0.1", 220 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 221 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 222 | }, 223 | "methods": { 224 | "version": "1.1.2", 225 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 226 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 227 | }, 228 | "mime": { 229 | "version": "1.6.0", 230 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 231 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 232 | }, 233 | "mime-db": { 234 | "version": "1.44.0", 235 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", 236 | "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" 237 | }, 238 | "mime-types": { 239 | "version": "2.1.27", 240 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", 241 | "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", 242 | "requires": { 243 | "mime-db": "1.44.0" 244 | } 245 | }, 246 | "ms": { 247 | "version": "2.0.0", 248 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 249 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 250 | }, 251 | "negotiator": { 252 | "version": "0.6.2", 253 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", 254 | "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" 255 | }, 256 | "on-finished": { 257 | "version": "2.3.0", 258 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 259 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 260 | "requires": { 261 | "ee-first": "1.1.1" 262 | } 263 | }, 264 | "parseurl": { 265 | "version": "1.3.3", 266 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 267 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 268 | }, 269 | "path-to-regexp": { 270 | "version": "0.1.7", 271 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 272 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 273 | }, 274 | "proxy-addr": { 275 | "version": "2.0.6", 276 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", 277 | "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", 278 | "requires": { 279 | "forwarded": "~0.1.2", 280 | "ipaddr.js": "1.9.1" 281 | } 282 | }, 283 | "qs": { 284 | "version": "6.7.0", 285 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", 286 | "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" 287 | }, 288 | "range-parser": { 289 | "version": "1.2.1", 290 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 291 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 292 | }, 293 | "raw-body": { 294 | "version": "2.4.0", 295 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", 296 | "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", 297 | "requires": { 298 | "bytes": "3.1.0", 299 | "http-errors": "1.7.2", 300 | "iconv-lite": "0.4.24", 301 | "unpipe": "1.0.0" 302 | } 303 | }, 304 | "safe-buffer": { 305 | "version": "5.1.2", 306 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 307 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 308 | }, 309 | "safer-buffer": { 310 | "version": "2.1.2", 311 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 312 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 313 | }, 314 | "send": { 315 | "version": "0.17.1", 316 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", 317 | "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", 318 | "requires": { 319 | "debug": "2.6.9", 320 | "depd": "~1.1.2", 321 | "destroy": "~1.0.4", 322 | "encodeurl": "~1.0.2", 323 | "escape-html": "~1.0.3", 324 | "etag": "~1.8.1", 325 | "fresh": "0.5.2", 326 | "http-errors": "~1.7.2", 327 | "mime": "1.6.0", 328 | "ms": "2.1.1", 329 | "on-finished": "~2.3.0", 330 | "range-parser": "~1.2.1", 331 | "statuses": "~1.5.0" 332 | }, 333 | "dependencies": { 334 | "ms": { 335 | "version": "2.1.1", 336 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 337 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 338 | } 339 | } 340 | }, 341 | "serve-static": { 342 | "version": "1.14.1", 343 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", 344 | "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", 345 | "requires": { 346 | "encodeurl": "~1.0.2", 347 | "escape-html": "~1.0.3", 348 | "parseurl": "~1.3.3", 349 | "send": "0.17.1" 350 | } 351 | }, 352 | "setprototypeof": { 353 | "version": "1.1.1", 354 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", 355 | "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" 356 | }, 357 | "statuses": { 358 | "version": "1.5.0", 359 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 360 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 361 | }, 362 | "toidentifier": { 363 | "version": "1.0.0", 364 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", 365 | "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" 366 | }, 367 | "type-is": { 368 | "version": "1.6.18", 369 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 370 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 371 | "requires": { 372 | "media-typer": "0.3.0", 373 | "mime-types": "~2.1.24" 374 | } 375 | }, 376 | "unpipe": { 377 | "version": "1.0.0", 378 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 379 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 380 | }, 381 | "utils-merge": { 382 | "version": "1.0.1", 383 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 384 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 385 | }, 386 | "vary": { 387 | "version": "1.1.2", 388 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 389 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /authentication/oauth-authorization-code/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-authorization-code-oauth-sample", 3 | "version": "0.0.1", 4 | "description": "Demo project to make authenticated requests to Uphold's API using the authorization code OAuth flow", 5 | "license": "MIT", 6 | "type": "module", 7 | "main": "app.js", 8 | "dependencies": { 9 | "axios": "^0.20.0", 10 | "dotenv": "^8.2.0", 11 | "express": "^4.17.1" 12 | }, 13 | "engines": { 14 | "node": ">=13.14" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/.env.example: -------------------------------------------------------------------------------- 1 | # Base Url endpoint 2 | BASE_URL = 'https://api-sandbox.uphold.com' 3 | 4 | CLIENT_ID = '' 5 | CLIENT_SECRET = '' 6 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/README.md: -------------------------------------------------------------------------------- 1 | # Client credentials OAuth flow 2 | 3 | This sample project demonstrates how to authenticate in the Uphold API using the [OAuth 2.0 client credentials](https://oauth.net/2/grant-types/client-credentials/) flow. 4 | For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). 5 | 6 | ## Summary 7 | 8 | **Ideal for backend integrations** that do not require access to other Uphold user accounts. 9 | 10 | This sample project performs the following actions: 11 | 12 | - Obtain an access token using client credentials authentication 13 | - Perform an API request (list assets) using the token 14 | 15 | **Important notice:** In Uphold's production environment, client credentials authentication is only available for **business accounts**, and requires manual approval from Uphold. 16 | Please [contact Uphold](mailto:developer@uphold.com) to obtain this permission. 17 | For requests made in the sandbox environment, as is the case with this demo project, those requirements can be skipped. 18 | 19 | ## Requirements 20 | 21 | - Node.js v13.14.0 or later 22 | - An account at 23 | 24 | ## Setup 25 | 26 | - Run `npm install` (or `yarn install`) 27 | - [Create an app on Uphold Sandbox](https://sandbox.uphold.com/dashboard/profile/applications/developer/new) and note its client ID and secret 28 | - Create a `.env` file based on the `.env.example` file, and populate it with the required data 29 | 30 | ## Run 31 | 32 | - Run `node index.js` 33 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/client-credentials-flow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import axios from "axios"; 6 | import dotenv from "dotenv"; 7 | import path from "path"; 8 | 9 | // Dotenv configuration. 10 | dotenv.config({ path: path.resolve() + "/.env" }); 11 | 12 | // Authentication credentials. 13 | const auth = Buffer.from(process.env.CLIENT_ID + ":" + process.env.CLIENT_SECRET).toString("base64"); 14 | 15 | /** 16 | * Format API error response for printing in console. 17 | */ 18 | 19 | function formatError(error) { 20 | const responseStatus = `${error.response.status} (${error.response.statusText})`; 21 | 22 | console.log( 23 | `Request failed with HTTP status code ${responseStatus}`, 24 | JSON.stringify({ 25 | url: error.config.url, 26 | response: error.response.data 27 | }, null, 2) 28 | ); 29 | 30 | throw error; 31 | } 32 | 33 | /** 34 | * Get a new access token, using client credentials authentication (client ID and secret). 35 | */ 36 | 37 | export async function getAccessToken() { 38 | try { 39 | const response = await axios.request({ 40 | method: "POST", 41 | url: `${process.env.BASE_URL}/oauth2/token`, 42 | data: "grant_type=client_credentials", 43 | headers: { 44 | Authorization: `Basic ${auth}`, 45 | "content-type": "application/x-www-form-urlencoded", 46 | }, 47 | }); 48 | 49 | return response.data; 50 | } catch (error) { 51 | formatError(error); 52 | } 53 | } 54 | 55 | /** 56 | * Get data about the currently authenticated user. 57 | */ 58 | 59 | export async function getUserInfo(accessToken) { 60 | try { 61 | const response = await axios.request({ 62 | method: "GET", 63 | url: `${process.env.BASE_URL}/v0/me`, 64 | headers: { 65 | Authorization: `Bearer ${accessToken}`, 66 | }, 67 | }); 68 | 69 | return response.data; 70 | } catch (error) { 71 | formatError(error); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import { getUserInfo, getAccessToken } from "./client-credentials-flow.js"; 6 | import fs from "fs"; 7 | 8 | (async () => { 9 | // Check for the .env file. 10 | if (fs.existsSync('./.env') === false) { 11 | console.log("Missing .env file. Please follow the steps described in the README."); 12 | return; 13 | } 14 | 15 | try { 16 | // Get a new access token using client credentials authentication. 17 | const token = await getAccessToken(); 18 | console.log(`Successfully obtained a new access token:`, token.access_token); 19 | 20 | // Test the new token by making an authenticated call to the API. 21 | const userData = await getUserInfo(token.access_token); 22 | console.log("Output from test API call:", userData); 23 | } catch (error) { 24 | // Unexpected error. 25 | return; 26 | } 27 | })(); 28 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-client-credentials-oauth-sample", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "axios": { 8 | "version": "0.20.0", 9 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", 10 | "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", 11 | "requires": { 12 | "follow-redirects": "^1.10.0" 13 | } 14 | }, 15 | "dotenv": { 16 | "version": "8.2.0", 17 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 18 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 19 | }, 20 | "follow-redirects": { 21 | "version": "1.13.0", 22 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 23 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /authentication/oauth-client-credentials/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-client-credentials-oauth-sample", 3 | "version": "0.0.1", 4 | "description": "Demo project to make authenticated requests to Uphold's REST API using the client credentials OAuth flow", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "type": "module", 8 | "dependencies": { 9 | "axios": "^0.20.0", 10 | "dotenv": "^8.2.0" 11 | }, 12 | "engines": { 13 | "node": ">=13.14" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /authentication/personal-access-token/.env.example: -------------------------------------------------------------------------------- 1 | # Base Url endpoint 2 | BASE_URL = 'https://api-sandbox.uphold.com' 3 | 4 | USERNAME = 'my_registration_email@domain.com' 5 | PASSWORD = 'my_password_in_clear_text' 6 | PAT_DESCRIPTION = 'Put a Description Here' 7 | -------------------------------------------------------------------------------- /authentication/personal-access-token/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /authentication/personal-access-token/README.md: -------------------------------------------------------------------------------- 1 | # Personal Access Token (PAT) 2 | 3 | This sample project demonstrates how to authenticate in the Uphold API using a Personal Access Token (PAT). 4 | For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation) 5 | 6 | ## Summary 7 | 8 | **Ideal for scripts**, automated tools and command-line programs which remain under the control of your personal Uphold account. 9 | 10 | This sample project performs the following actions: 11 | 12 | - Create a new PAT 13 | - List all created PATs associated to this account 14 | 15 | **Important notice:** If the account has two-factor authentication (2FA) active, a one-time-password (OTP) must be passed when creating PATs. 16 | In the Sandbox environment, the special OTP value `000000` can be passed for convenience, as can be seen in the `index.js` file. In production, you would need to use an actual TOTP code provided by your chosen authenticator app (e.g. Google Authenticator). 17 | 18 | ## Requirements 19 | 20 | - Node.js v13.14.0 or later 21 | - An account at 22 | 23 | ## Setup 24 | 25 | - Run `npm install` (or `yarn install`). 26 | - Create a `.env` file based on the `.env.example` file, and populate it with the required data. 27 | 28 | ## Run 29 | 30 | - Run `node index.js`. 31 | -------------------------------------------------------------------------------- /authentication/personal-access-token/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import { createNewPAT, getAuthenticationMethods, getUserPATs } from "./personal-access-token.js"; 6 | import fs from "fs"; 7 | 8 | (async () => { 9 | // Check for the .env file. 10 | if (fs.existsSync('./.env') === false) { 11 | console.log("Missing .env file. Please follow the steps described in the README."); 12 | return; 13 | } 14 | 15 | try { 16 | // Get list of authentication methods. 17 | const authMethods = await getAuthenticationMethods(); 18 | 19 | // In the Sandbox environment, the special OTP value `000000` can be passed for convenience. 20 | const totp = { 21 | OTPMethodId: "", 22 | OTPToken: "000000", 23 | }; 24 | 25 | // Try to determine if the authenticated account has two-factor authentication (2FA) active, 26 | // as that will require a one-time password (OTP) for the PAT creation request. 27 | const totpCheck = 28 | authMethods != null ? authMethods.find((x) => x.type === "totp") : null; 29 | if (totpCheck) { 30 | totp.OTPMethodId = totpCheck.id; 31 | } 32 | 33 | // Get a new Personal Access Token (PAT). 34 | const token = await createNewPAT(totp); 35 | console.log("Successfully obtained a new Personal Access Token (PAT):", token.accessToken); 36 | 37 | // Test the new token by making an authenticated call to the API. 38 | const userPATs = await getUserPATs(token.accessToken); 39 | console.log("Output from test API call:", userPATs); 40 | } catch (error) { 41 | // Unexpected error. 42 | return; 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /authentication/personal-access-token/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-pat-sample", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "axios": { 8 | "version": "0.20.0", 9 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", 10 | "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", 11 | "requires": { 12 | "follow-redirects": "^1.10.0" 13 | } 14 | }, 15 | "dotenv": { 16 | "version": "8.2.0", 17 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 18 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 19 | }, 20 | "follow-redirects": { 21 | "version": "1.13.0", 22 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 23 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /authentication/personal-access-token/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-pat-sample", 3 | "version": "0.0.1", 4 | "description": "Demo project to make authenticated requests to Uphold's REST API using a Personal Access Token (PAT)", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "type": "module", 8 | "dependencies": { 9 | "axios": "^0.20.0", 10 | "dotenv": "^8.2.0" 11 | }, 12 | "engines": { 13 | "node": ">=13.14" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /authentication/personal-access-token/personal-access-token.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import axios from "axios"; 6 | import dotenv from "dotenv"; 7 | import path from "path"; 8 | 9 | // Dotenv configuration. 10 | dotenv.config({ path: path.resolve() + "/.env" }); 11 | 12 | // Authentication credentials. 13 | const auth = Buffer.from(process.env.USERNAME + ":" + process.env.PASSWORD).toString("base64"); 14 | 15 | /** 16 | * Format API error response for printing in console. 17 | */ 18 | 19 | function formatError(error) { 20 | const responseStatus = `${error.response.status} (${error.response.statusText})`; 21 | 22 | console.log( 23 | `Request failed with HTTP status code ${responseStatus}`, 24 | JSON.stringify({ 25 | url: error.config.url, 26 | response: error.response.data 27 | }, null, 2) 28 | ); 29 | 30 | throw error; 31 | } 32 | 33 | /** 34 | * Get list of authentication methods, using basic authentication (username and password). 35 | */ 36 | 37 | export async function getAuthenticationMethods() { 38 | try { 39 | const response = await axios.request({ 40 | method: "GET", 41 | url: `${process.env.BASE_URL}/v0/me/authentication_methods`, 42 | headers: { 43 | Authorization: `Basic ${auth}`, 44 | }, 45 | }); 46 | 47 | return response.data; 48 | } catch (error) { 49 | formatError(error); 50 | } 51 | } 52 | 53 | /** 54 | * Create a Personal Access Token (PAT), using basic authentication (username and password). 55 | * The time-based one-time password (TOTP) parameter 56 | * is typically provided by an OTP application, e.g. Google Authenticator. 57 | */ 58 | 59 | export async function createNewPAT(totp) { 60 | const headers = { 61 | Authorization: `Basic ${auth}`, 62 | "content-type": "application/json", 63 | }; 64 | 65 | // Set OTP headers if the totp parameter is passed. 66 | if (totp.OTPMethodId) { 67 | headers["OTP-Method-Id"] = totp.OTPMethodId; 68 | headers["OTP-Token"] = totp.OTPToken; 69 | } 70 | 71 | try { 72 | const response = await axios.request({ 73 | method: "POST", 74 | url: `${process.env.BASE_URL}/v0/me/tokens`, 75 | data: { description: process.env.PAT_DESCRIPTION }, 76 | headers, 77 | }); 78 | 79 | return response.data; 80 | } catch (error) { 81 | formatError(error); 82 | } 83 | } 84 | 85 | /** 86 | * Get list of Personal Access Tokens (PATs), using an existing PAT. 87 | */ 88 | 89 | export async function getUserPATs(accessToken) { 90 | try { 91 | const response = await axios.request({ 92 | method: "GET", 93 | url: `${process.env.BASE_URL}/v0/me/tokens`, 94 | headers: { 95 | Authorization: `Bearer ${accessToken}`, 96 | }, 97 | }); 98 | 99 | return response.data; 100 | } catch (error) { 101 | formatError(error); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/.env.example: -------------------------------------------------------------------------------- 1 | # Base Url endpoint 2 | BASE_URL = 'https://api-sandbox.uphold.com' 3 | 4 | ACCESS_TOKEN = '' 5 | DESTINATION_EMAIL_ACCOUNT = 'asdf.asdf@email.com' 6 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/README.md: -------------------------------------------------------------------------------- 1 | # User-to-user transaction 2 | 3 | This sample project demonstrates how to perform a transaction from one Uphold user to another, 4 | with the latter identified by their email address. 5 | For further background, please refer to the [API documentation](https://uphold.com/en/developer/api/documentation). 6 | 7 | ## Summary 8 | 9 | This sample project performs the following actions: 10 | 11 | - Create and commit a transaction 12 | - Display the data about the transaction 13 | 14 | ## Requirements 15 | 16 | - Node.js v13.14.0 or later 17 | - An account at with at least $1 USD of available funds 18 | - An access token from that account, to perform authenticated requests to the Uphold API 19 | (see the [authentication](../../authentication) examples for how to obtain one) 20 | 21 | ## Setup 22 | 23 | - Run `npm install` (or `yarn install`) 24 | - Create a `.env` file based on the `.env.example` file, and populate it with the required data. 25 | 26 | ## Run 27 | 28 | Run `node index.js`. 29 | 30 | The code will locate a card with nonzero balance in the source account, and prepare a $1 USD transaction 31 | from that card to the account identified by the email in the `.env` file. 32 | 33 | The result will depend on the status of the destination email: 34 | 35 | - If it is already associated with an existing Sandbox account, the transaction will be completed immediately 36 | and the funds will become available in the recipient's account. 37 | - If no Sandbox account exists with that email, an "invite"-type transaction will be created, 38 | which will be executed when the associated account is created. 39 | This invite can be cancelled by the sender while the recipient hasn't registered 40 | (which is useful if you use a dummy email address for this). 41 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/assets/transaction-scopes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uphold/rest-api-examples/59d91b8945413009637003f9400dcdd51d7ab28b/transactions/user-to-user-transaction/assets/transaction-scopes.png -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import { createAndCommitTransaction, getCardWithFunds } from "./user-to-user-transaction.js"; 6 | import fs from "fs"; 7 | 8 | (async () => { 9 | // Check for the .env file. 10 | if (fs.existsSync('./.env') === false) { 11 | console.log("Missing .env file. Please follow the steps described in the README."); 12 | return; 13 | } 14 | 15 | try { 16 | // Locate a card that can be used as the source for the transaction. 17 | const sourceCard = await getCardWithFunds(); 18 | 19 | // Create a transaction and log its outputs 20 | console.log(await createAndCommitTransaction(sourceCard.id)); 21 | } catch (error) { 22 | // Unexpected error. 23 | return; 24 | } 25 | })(); 26 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-transaction-sample", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "axios": { 8 | "version": "0.20.0", 9 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", 10 | "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", 11 | "requires": { 12 | "follow-redirects": "^1.10.0" 13 | } 14 | }, 15 | "dotenv": { 16 | "version": "8.2.0", 17 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", 18 | "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" 19 | }, 20 | "follow-redirects": { 21 | "version": "1.13.0", 22 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", 23 | "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uphold-transaction-sample", 3 | "version": "0.0.1", 4 | "description": "Demo project to perform a transaction from one Uphold user to another using Uphold's API", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "type": "module", 8 | "dependencies": { 9 | "axios": "^0.20.0", 10 | "dotenv": "^8.2.0" 11 | }, 12 | "engines": { 13 | "node": ">=13.14" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /transactions/user-to-user-transaction/user-to-user-transaction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dependencies. 3 | */ 4 | 5 | import axios from "axios"; 6 | import dotenv from "dotenv"; 7 | import path from "path"; 8 | 9 | // Dotenv configuration. 10 | dotenv.config({ path: path.resolve() + "/.env" }); 11 | 12 | /** 13 | * Format API error response for printing in console. 14 | */ 15 | 16 | function formatError(error) { 17 | const responseStatus = `${error.response.status} (${error.response.statusText})`; 18 | 19 | console.log( 20 | `Request failed with HTTP status code ${responseStatus}`, 21 | JSON.stringify({ 22 | url: error.config.url, 23 | response: error.response.data 24 | }, null, 2) 25 | ); 26 | 27 | throw error; 28 | } 29 | 30 | /** 31 | * Create and commit a 1 USD transaction from a specific card. 32 | */ 33 | 34 | export async function createAndCommitTransaction(sourceCardID = null) { 35 | try { 36 | const response = await axios.request({ 37 | method: "POST", 38 | url: `${process.env.BASE_URL}/v0/me/cards/${sourceCardID}/transactions?commit=true`, 39 | data: { 40 | denomination: { 41 | amount: "1", 42 | currency: "USD", 43 | }, 44 | destination: `${process.env.DESTINATION_EMAIL_ACCOUNT}`, 45 | }, 46 | headers: { 47 | Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, 48 | "content-type": "application/json", 49 | }, 50 | }); 51 | 52 | return response.data; 53 | } catch (error) { 54 | formatError(error); 55 | } 56 | } 57 | 58 | /** 59 | * Get the first card with available balance (if one exists). 60 | */ 61 | 62 | export async function getCardWithFunds() { 63 | try { 64 | const response = await axios.request({ 65 | method: "GET", 66 | url: `${process.env.BASE_URL}/v0/me/cards`, 67 | headers: { 68 | Authorization: `Bearer ${process.env.ACCESS_TOKEN}`, 69 | }, 70 | }); 71 | 72 | // Get the the first card with nonzero available balance. 73 | return response.data.filter(card => { 74 | return Number(card.available) > 0 75 | })[0]; 76 | } catch (error) { 77 | formatError(error); 78 | } 79 | } 80 | --------------------------------------------------------------------------------