├── .gitignore ├── .prettierrc.js ├── .eslintrc.json ├── .env.example ├── examples ├── account-creation │ └── generate-accounts.js ├── configuration │ └── configure-sdk.js ├── block-search │ ├── autoexecute-specific-blocknumber-search.js │ ├── autoexecute-specific-blockid-search.js │ └── autoexecute-latest-block-search.js ├── transaction-search │ └── autoexecute-transaction-search.js ├── state-search │ ├── autoexecute-utxo-search.js │ └── autoexecute-accounts-search.js └── transaction-creation │ └── submit-transactions.js ├── package.json ├── .vscode └── launch.json ├── README.md ├── Exercise5.md ├── Exercise6.md ├── Exercise3.md ├── Exercise4.md ├── Exercise2.md ├── Exercise1.md └── Exercise7.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .env.enc -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | useTabs: false, 3 | semi: true, 4 | printWidth: 80, 5 | tabWidth: 2, 6 | trailingComma: "all", 7 | singleQuote: false, 8 | }; -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "no-unused-vars":"warn", 6 | "prettier/prettier": "error" 7 | } 8 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | USER_NAME=your-overledger-devportal-email-address-here 2 | PASSWORD=your-overledger-devportal-password-here 3 | CLIENT_ID=your-overledger-devportal-application-client-id-here 4 | CLIENT_SECRET=your-overledger-devportal-application-client-secret-here 5 | 6 | BITCOIN_PRIVATE_KEY=bitcoin_private_key 7 | ETHEREUM_PRIVATE_KEY=ethereum_private_key 8 | XRP_LEDGER_PRIVATE_KEY=xrp_ledger_private_key 9 | 10 | BITCOIN_ADDRESS=bitcoin_address 11 | ETHEREUM_ADDRESS=ethereum_address 12 | XRP_LEDGER_ADDRESS=xrp_ledger_address -------------------------------------------------------------------------------- /examples/account-creation/generate-accounts.js: -------------------------------------------------------------------------------- 1 | const OverledgerSDK = require("@quantnetwork/overledger-bundle").default; 2 | const { DltNameOptions } = require("@quantnetwork/overledger-types"); 3 | const log4js = require("log4js"); 4 | 5 | const courseModule = "generate-accounts"; 6 | const log = log4js.getLogger(courseModule); 7 | 8 | // Initialize log 9 | log4js.configure({ 10 | appenders: { 11 | console: { type: "console" }, 12 | }, 13 | categories: { 14 | default: { appenders: ["console"], level: "debug" }, 15 | }, 16 | }); 17 | 18 | log.info("Initializing the SDK"); 19 | (async () => { 20 | try { 21 | const overledger = new OverledgerSDK({ 22 | dlts: [ 23 | { dlt: DltNameOptions.BITCOIN }, 24 | { dlt: DltNameOptions.ETHEREUM }, 25 | { dlt: DltNameOptions.XRP_LEDGER }, 26 | ], 27 | provider: { network: "testnet" }, 28 | }); 29 | 30 | log.info("Creating the Accounts"); 31 | const bitcoinAccount = await overledger.dlts.bitcoin.createAccount(); 32 | log.info(`BitcoinAccount =\n${JSON.stringify(bitcoinAccount)}\n`); 33 | 34 | const ethAccount = await overledger.dlts.ethereum.createAccount(); 35 | log.info(`EthereumAccount =\n${JSON.stringify(ethAccount)}\n`); 36 | 37 | const xrpAccount = await overledger.dlts["xrp-ledger"].createAccount(); 38 | log.info(`XRPLedgerAccount =\n${JSON.stringify(xrpAccount)}\n`); 39 | } catch (e) { 40 | log.error("error", e); 41 | } 42 | })(); 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quant-course-exercises", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "generate-env": "secure-env .env -s MY_PASSWORD", 8 | "format:lint": "eslint 'examples/**/*.js' --quiet --fix", 9 | "format:prettier": "prettier --config ./.prettierrc.js 'examples/**/*.js' --write" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/RafaelAPB/quant-blockchain-developer-course-examples.git" 14 | }, 15 | "contributors": [ 16 | { 17 | "name": "Luke Riley", 18 | "email": "luke.riley@quant.network", 19 | "url": "https://www.quant.network/" 20 | }, 21 | { 22 | "name": "Rafael Belchior", 23 | "email": "rafael.belchior@tecnico.ulisboa.pt", 24 | "url": "https://rafaelapb.github.io/" 25 | } 26 | ], 27 | "license": "Apache 2-0", 28 | "bugs": { 29 | "url": "https://github.com/RafaelAPB/quant-blockchain-developer-course-examples/issues" 30 | }, 31 | "homepage": "https://github.com/RafaelAPB/quant-blockchain-developer-course-examples#readme", 32 | "dependencies": { 33 | "@quantnetwork/overledger-bundle": "^2.4.0", 34 | "@quantnetwork/overledger-types": "^2.4.0", 35 | "log4js": "^6.4.1", 36 | "npm-run": "^5.0.1", 37 | "secure-env": "^1.2.0" 38 | }, 39 | "devDependencies": { 40 | "eslint": "^8.2.0", 41 | "eslint-config-airbnb": "^19.0.2", 42 | "eslint-config-node": "^2.0.0", 43 | "eslint-config-prettier": "^8.3.0", 44 | "eslint-plugin-import": "^2.25.3", 45 | "eslint-plugin-jsx-a11y": "^6.5.1", 46 | "eslint-plugin-node": "^11.1.0", 47 | "eslint-plugin-prettier": "^4.0.0", 48 | "prettier": "^2.5.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/configuration/configure-sdk.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | 6 | const OverledgerSDK = OverledgerBundle.default; 7 | const courseModule = "configure-credentials"; 8 | const log = log4js.getLogger(courseModule); 9 | 10 | // Initialize log 11 | log4js.configure({ 12 | appenders: { 13 | console: { type: "console" }, 14 | }, 15 | categories: { 16 | default: { appenders: ["console"], level: "debug" }, 17 | }, 18 | }); 19 | 20 | log.info("Loading secure environment variables defined in .env.enc"); 21 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 22 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 23 | 24 | // Check for provided password for the secure env 25 | if (!SENV_PASSWORD) { 26 | log.error( 27 | "Please insert a password to decrypt the secure env file. Example: \n node generate-credentials.js password=MY_PASSWORD", 28 | ); 29 | throw new Error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node generate-credentials.js password=MY_PASSWORD", 31 | ); 32 | } 33 | log.info("Executing ", courseModule); 34 | (async () => { 35 | try { 36 | log.info("Initialize the SDK"); 37 | const overledger = new OverledgerSDK({ 38 | dlts: [], 39 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 40 | provider: { network: "https://api.sandbox.overledger.io/" }, 41 | envFilePassword: SENV_PASSWORD, 42 | }); 43 | log.info("Obtain Access Token to interact with Overledger "); 44 | const refreshTokensResponse = 45 | await overledger.getTokensUsingClientIdAndSecret( 46 | process.env.USER_NAME, 47 | process.env.PASSWORD, 48 | process.env.CLIENT_ID, 49 | process.env.CLIENT_SECRET, 50 | ); 51 | log.info("accessToken:\n", refreshTokensResponse.accessToken); 52 | } catch (e) { 53 | log.error("error", e); 54 | } 55 | })(); 56 | -------------------------------------------------------------------------------- /examples/block-search/autoexecute-specific-blocknumber-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "specific-blocknumber-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-specific-blocknumber-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-specific-blocknumber-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | log.info("Executing ", courseModule); 37 | (async () => { 38 | try { 39 | log.info("Initializing the SDK"); 40 | const overledger = new OverledgerSDK({ 41 | dlts: [ 42 | { dlt: DltNameOptions.BITCOIN }, 43 | { dlt: DltNameOptions.ETHEREUM }, 44 | { dlt: DltNameOptions.XRP_LEDGER }, 45 | ], // connects OVL to these 3 technologies 46 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 47 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 48 | envFilePassword: SENV_PASSWORD, 49 | }); 50 | 51 | log.info("Obtaining the Access Token to interact with Overledger"); 52 | const refreshTokensResponse = 53 | await overledger.getTokensUsingClientIdAndSecret( 54 | process.env.USER_NAME, 55 | process.env.PASSWORD, 56 | process.env.CLIENT_ID, 57 | process.env.CLIENT_SECRET, 58 | ); 59 | 60 | log.info("Createing the Overledger Request Object with Correct Location"); 61 | const overledgerRequestMetaData = { 62 | location: { 63 | technology: "XRP Ledger", 64 | network: "Testnet", 65 | }, 66 | }; 67 | const overledgerInstance = overledger.provider.createRequest( 68 | refreshTokensResponse.accessToken.toString(), 69 | ); 70 | 71 | log.info("Sending a Request to Overledger for the Latest Block"); 72 | const overledgerLatestBlockResponse = await overledgerInstance.post( 73 | "/autoexecution/search/block/latest", 74 | overledgerRequestMetaData, 75 | ); 76 | 77 | log.info("Sending a Request to Overledger for the Parent Block (Using BlockNumber Search)"); 78 | const parentBlockNumber = 79 | overledgerLatestBlockResponse.data.executionBlockSearchResponse.block 80 | .number - 1; 81 | const overledgerParentBlockResponse = await overledgerInstance.post( 82 | `/autoexecution/search/block/${parentBlockNumber}`, 83 | overledgerRequestMetaData, 84 | ); 85 | 86 | log.info( 87 | `Printing Out Overledger's Response:\n\n${JSON.stringify( 88 | overledgerParentBlockResponse.data, 89 | )}\n\n`, 90 | ); 91 | } catch (e) { 92 | log.error("error", e); 93 | } 94 | })(); 95 | -------------------------------------------------------------------------------- /examples/block-search/autoexecute-specific-blockid-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "specific-blockid-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-specific-blockid-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-specific-blockid-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | log.info("Executing ", courseModule); 37 | (async () => { 38 | try { 39 | log.info("Initializing the SDK"); 40 | const overledger = new OverledgerSDK({ 41 | dlts: [ 42 | { dlt: DltNameOptions.BITCOIN }, 43 | { dlt: DltNameOptions.ETHEREUM }, 44 | { dlt: DltNameOptions.XRP_LEDGER }, 45 | ], // connects OVL to these 3 technologies 46 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 47 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 48 | envFilePassword: SENV_PASSWORD, 49 | }); 50 | 51 | log.info("Obtaining the Access Token to Interact with Overledger"); 52 | const refreshTokensResponse = 53 | await overledger.getTokensUsingClientIdAndSecret( 54 | process.env.USER_NAME, 55 | process.env.PASSWORD, 56 | process.env.CLIENT_ID, 57 | process.env.CLIENT_SECRET, 58 | ); 59 | 60 | log.info( 61 | "Creating the Overledger Request Object with the Correct Location", 62 | ); 63 | const overledgerRequestMetaData = { 64 | location: { 65 | technology: "Ethereum", 66 | network: "Ethereum Goerli Testnet", 67 | }, 68 | }; 69 | const overledgerInstance = overledger.provider.createRequest( 70 | refreshTokensResponse.accessToken.toString(), 71 | ); 72 | 73 | log.info("Sending a Request to Overledger for the Latest Block"); 74 | const overledgerLatestBlockResponse = await overledgerInstance.post( 75 | "/autoexecution/search/block/latest", 76 | overledgerRequestMetaData, 77 | ); 78 | 79 | log.info("Sending a Request to Overledger for the Parent Block (Using BlockId Search)"); 80 | const parentBlockId = 81 | overledgerLatestBlockResponse.data.executionBlockSearchResponse.block 82 | .linkedBlocks.parent; 83 | const overledgerParentBlockResponse = await overledgerInstance.post( 84 | `/autoexecution/search/block/${parentBlockId}`, 85 | overledgerRequestMetaData, 86 | ); 87 | 88 | log.info( 89 | `Printing Out Overledger's Response:\n\n${JSON.stringify( 90 | overledgerParentBlockResponse.data, 91 | )}\n\n`, 92 | ); 93 | } catch (e) { 94 | log.error("error", e); 95 | } 96 | })(); 97 | -------------------------------------------------------------------------------- /examples/block-search/autoexecute-latest-block-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "latest-block-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-latest-block-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/block-search/autoexecute-latest-block-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | 37 | //if password provided continue 38 | log.info("Executing ", courseModule); 39 | (async () => { 40 | try { 41 | log.info("Initializing the SDK"); 42 | const overledger = new OverledgerSDK({ 43 | dlts: [ 44 | { dlt: DltNameOptions.BITCOIN }, 45 | { dlt: DltNameOptions.ETHEREUM }, 46 | { dlt: DltNameOptions.XRP_LEDGER }, 47 | ], // connects OVL to these 3 technologies 48 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 49 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 50 | envFilePassword: SENV_PASSWORD, 51 | }); 52 | 53 | log.info("Obtaining the Access Token to Interact with Overledger"); 54 | const refreshTokensResponse = 55 | await overledger.getTokensUsingClientIdAndSecret( 56 | process.env.USER_NAME, 57 | process.env.PASSWORD, 58 | process.env.CLIENT_ID, 59 | process.env.CLIENT_SECRET, 60 | ); 61 | 62 | log.info("Creating Overledger Request Object with the Correct Location"); 63 | const overledgerRequestMetaData = { 64 | location: { 65 | technology: "Bitcoin", 66 | network: "Testnet", 67 | }, 68 | }; 69 | const overledgerInstance = overledger.provider.createRequest( 70 | refreshTokensResponse.accessToken.toString(), 71 | ); 72 | 73 | log.info("Sending a Request to Overledger for the Latest Block"); 74 | const overledgerResponse = await overledgerInstance.post( 75 | "/autoexecution/search/block/latest", 76 | overledgerRequestMetaData, 77 | ); 78 | 79 | log.info(`Printing Out Overledger's Response:\n\n`); 80 | 81 | // The preparation object section of the response includes 82 | // the request id and any QNT fee that must be paid for use of this endpoint 83 | // (API calls are free on testnets). 84 | log.info( 85 | `preparationBlockSearchResponse: ${JSON.stringify( 86 | overledgerResponse.data.preparationBlockSearchResponse, 87 | )}`, 88 | ); 89 | 90 | // The execution object section of the response includes a location, status, and block objects 91 | log.info( 92 | `executionBlockSearchResponse (location): ${JSON.stringify( 93 | overledgerResponse.data.executionBlockSearchResponse.location, 94 | )}`, 95 | ); 96 | log.info( 97 | `executionBlockSearchResponse (status): ${JSON.stringify( 98 | overledgerResponse.data.executionBlockSearchResponse.status, 99 | )}`, 100 | ); 101 | log.info( 102 | `executionBlockSearchResponse (block): ${JSON.stringify( 103 | overledgerResponse.data.executionBlockSearchResponse.block, 104 | )}`, 105 | ); 106 | } catch (e) { 107 | log.error("error", e); 108 | } 109 | })(); 110 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "TAP: Current TS Test File", 9 | "runtimeExecutable": "node", 10 | "type": "node", 11 | "request": "launch", 12 | "protocol": "inspector", 13 | "env": { 14 | "TS_NODE_PROJECT": "tsconfig.json", 15 | "OFF-HFC_LOGGING": "{\"debug\":\"console\",\"info\":\"console\"}" 16 | }, 17 | "args": [ 18 | "--async-stack-traces", 19 | "${relativeFile}" 20 | ], 21 | "runtimeArgs": [ 22 | "--require", 23 | "ts-node/register" 24 | ], 25 | "console": "integratedTerminal", 26 | "sourceMaps": true, 27 | "sourceMapPathOverrides": { 28 | "webpack://cactus-*": "${workspaceRoot}/packages/cactus-*", 29 | }, 30 | // "outFiles": [ 31 | // "${workspaceRoot}/packages/cactus-*/dist/**/*" 32 | // ], 33 | "cwd": "${workspaceRoot}", 34 | "skipFiles": [ 35 | "/**", 36 | ] 37 | }, 38 | { 39 | "type": "node", 40 | "request": "launch", 41 | "name": "TAP: Current JS Test File", 42 | "console": "integratedTerminal", 43 | "program": "${workspaceFolder}/${relativeFile}", 44 | "cwd": "${workspaceFolder}", 45 | "runtimeArgs": [ 46 | "--async-stack-traces" 47 | ], 48 | "args": [ 49 | "--password=MY_PASSWORD", "--fundingTx=cd00aa114c0390f744c6a9615f7902f21de835e7df629e7d8d1028cdb58d4a41" 50 | ], 51 | "outFiles": [ 52 | "dist/lib/*" 53 | ], 54 | "env": {}, 55 | }, 56 | { 57 | "type": "node", 58 | "request": "launch", 59 | "name": "cmd-api-server", 60 | // "program": "${workspaceFolder}/packages/cactus-cmd-api-server/src/main/typescript/cmd/cactus-api.ts", 61 | "program": "${workspaceFolder}/packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js", 62 | "runtimeArgs": [ 63 | "--preserve-symlinks", 64 | "--preserve-symlinks-main" 65 | ], 66 | // "preLaunchTask": "npm: build:dev:cmd-api-server", 67 | "args": [ 68 | "--public-key=03aa57b5c6506a6e5a2851dcbc14bf2b3d2b9196aecacc946f630eab5203dca8c4", 69 | "--private-key=da43d3ce06f7b0eef447ca209c00cf2efdef02a761fb5ba2aaf7fc601ceaf555", 70 | "--api-cors-domain-csv=*", 71 | "--config-file=.config.json" 72 | ], 73 | "sourceMaps": true, 74 | "sourceMapPathOverrides": { 75 | // "webpack://hana-workbench-api-server/../*": "${workspaceRoot}/pkg/*", 76 | "webpack://cactus-*": "${workspaceRoot}/packages/cactus-*", 77 | // "webpack://cactus-common/./*": "${workspaceFolder}/packages/cactus-common/*", 78 | }, 79 | // "outFiles": [ 80 | // "${workspaceRoot}/packages/cactus-*/dist/lib/**/*", 81 | // ], 82 | "skipFiles": [ 83 | "/**" 84 | ] 85 | }, 86 | { 87 | "name": "webpack:dev (Launch via NPM)", 88 | "request": "launch", 89 | "runtimeArgs": [ 90 | "run-script", 91 | "webpack:dev" 92 | ], 93 | "runtimeExecutable": "npm", 94 | "skipFiles": [ 95 | "/**" 96 | ], 97 | "type": "pwa-node" 98 | }, 99 | { 100 | "name": "TAP: Current TS Test File v2", 101 | "type": "node", 102 | "request": "launch", 103 | "protocol": "inspector", 104 | "env": { 105 | "TS_NODE_PROJECT": "tsconfig.json" 106 | }, 107 | "args": [ 108 | "${relativeFile}" 109 | ], 110 | "runtimeArgs": [ 111 | "-r", 112 | "ts-node/register" 113 | ], 114 | "console": "integratedTerminal", 115 | "sourceMaps": true, 116 | "cwd": "${workspaceRoot}" 117 | }, 118 | ] 119 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Become a Blockchain Developer: Foundations 2 | ![](https://img.shields.io/github/issues/quantnetwork/blockchain-developer-exercises-foundations) ![](https://img.shields.io/github/forks/quantnetwork/blockchain-developer-exercises-foundations) ![](https://img.shields.io/github/stars/quantnetwork/blockchain-developer-exercises-foundations) 3 | 4 | Welcome to the repository for the developer tasks of the Future Learn's **open-source course** [Become a Blockchain Developer: Foundations Edition](https://www.futurelearn.com/courses/become-a-blockchain-developer-foundations) 🎓. 5 | 6 | This course aims at providing a reliable basis for developers to get started in the blockchain and distributed ledger technology domain. 7 | 8 | This course is mainly divided into **articles** and **developer exercises**, where the articles explain the theoretical foundations behind what is applied in the exercises. To access the articles you will need to sign up on [Future Learn](https://www.futurelearn.com/courses/become-a-blockchain-developer-foundations). This is a course that you can take and complete at any time (i.e. there are no live classes). Note that there are free and paid access levels for this course. Consult the "Ways to Learn" section of the course page for more information. 9 | 10 | 11 | ## Course Outcomes 12 | 13 | At the end of this course you will have gained both theoretical and practical knowledge about distributed ledger technologies, where the outcomes of both will be as follows: 14 | 15 | ### Theoretical Outcomes 16 | Reading and understanding the course articles will provide you with the knowledge to understand the key foundational aspects of blockchains and other distributed ledger technologies. In summary you will have learned: 17 | 18 | - What defines a ledger 19 | - What components make up distributed ledger technologies 20 | - What exactly is a blockchain 21 | - How distributed ledgers can be organised in an Unspent Transaction Output (UTXO) or Accounts based method 22 | - What cryptography underpins distributed ledger technologies 23 | 24 | ### Practical Outcomes 25 | Completing the course exercises will provide you with the foundational practical experience to start creating multi-ledger decentralised applications (mDapps). In summary you have learned: 26 | 27 | - How to interact with multiple distributed ledger technologies (specifically Bitcoin, Ethereum and the XRP Ledger) 28 | - How to connect to multiple distributed ledger technology networks (specifically the test networks of the previously listed technologies) 29 | - How to read blocks, transactions and state information from these DLT networks 30 | - How to add new transactions to these DLT networks 31 | 32 | The practical elements of this course are made possible via utilising Quant's Overledger. When new DLT networks are added to Overledger's public version (through new Overledger release versions), the exercises of this course will easily run against any newly connected DLT network via a simple change to one parameter (the location object), as explained later. 33 | 34 | ## Future Courses 35 | 36 | Future courses will expand on this course's outcomes to show how more complicated multi-ledger decentralised applications can be built. Currently the second course is scheduled to cover in detail smart contracts, tokens and consensus protocols, whereas the third course is scheduled to cover interoperability and scalability in detail. 37 | 38 | 39 | ## Issues, Suggestions & Improvements 40 | 41 | Issues related to these exercises can be discussed with your fellow learners in the FutureLearn course discussion boards. Quant technical support can be contacted at support@quant.zendesk.com. Finally, if you have suggested changes, you can raise them in this github repo via pull requests method. 42 | 43 | ## Maintainers 44 | 45 | | Mainteiner | Github | Email | 46 | |----------------- |------------------------------------------- |------------------------------------ | 47 | | Dr. Luke Riley | [lukerQuant](https://github.com/lukerQuant) | luke.riley@quant.network | 48 | 49 | ## Acknowledgements 50 | 51 | This course was developed through a collaboration between [Quant Network](https://www.quant.network/), [King's College London](https://www.kcl.ac.uk/) and [FutureLearn](https://www.futurelearn.com/). 52 | 53 | Authors have previously worked on related university modules. Luke developed King's College London's [Distributed Ledgers and Cryptocurrencies Module](https://rl.talis.com/3/kcl/lists/CB92E513-A866-5EFF-7E83-2EA72DF78D00.html?lang=en-GB) and Rafael developed Hyperledger Lab's Open Source [Enterprise Blockchain Technologies Course for Universities](https://github.com/hyperledger-labs/university-course). 54 | -------------------------------------------------------------------------------- /Exercise5.md: -------------------------------------------------------------------------------- 1 | # Week 2 Ledger State and Updates 2 | 3 | ## The Accounts Model 4 | 5 | ### Exercise - Read your first Accounts Transaction 6 | 7 | In this exercise, we will read our first Accounts transaction via Overledger’s autoExecuteSearchTransaction API. The documentation can be found [here](https://docs.overledger.io/#operation/autoExecuteSearchTransactionRequest). 8 | 9 | #### DLT Network Information 10 | 11 | We will be interacting with the Ethereum Goerli and the XRP Ledger testnets. The relevant Overledger location objects are as follows: 12 | 13 | 1. `Location = {“technology”: “Ethereum”, “network”: “Ethereum Goerli Testnet”}` 14 | 15 | 2. `Location = {“technology”: “XRP Ledger”, “network”: “Testnet”}` 16 | 17 | #### Prerequisites 18 | 19 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md) and that you have completed the previous exercises to search for a block using Overledger [here](./Exercise2.md) and to search for a UTXO transaction using Overledger [here](./Exercise3.md). 20 | 21 | 22 | ##### Overledger Auto Execute Transaction Search API Response 23 | 24 | See [here](./Exercise3.md) for further details on the response body. 25 | 26 | ###### Auto Execute Transaction Search API Response Origins and Destinations 27 | 28 | In the Account model, a payment transaction contains one origin and usually one destination. In the Account model for payment transactions the related identifiers have specific meaning: 29 | 30 | - OriginId: This is a reference to the (externally owned) account that is being debited the payment. 31 | 32 | - DestinationId: This is a reference to the account that is being credited the payment. 33 | 34 | In Ethereum and the XRP Ledger DLTs, there is only one destination for payment transactions. But this is not true for all Accounts based DLTs. For instance the Stellar DLT allows multiple payments in one transaction. 35 | 36 | Note that accounts based transactions that are contract invocations have a more complicated origin and destination structure. We will cover that in the next course. 37 | 38 | #### Challenges 39 | 40 | #### Searching for the Latest Payment Transaction 41 | 42 | We will demostrate searching for an Accounts transaction through this specific challenge. Given the example `examples/transaction-search/autoexecute-transaction-search.js` file (previously used in Exercise 3) and the location information listed above, can you understand how to change this file to instead return the latest payment transaction on the Ethereum Goerli and XRP Ledger testnets? Recall that the following is required to run the file: 43 | 44 | ``` 45 | node examples/transaction-search/autoexecute-transaction-search.js password=MY_PASSWORD 46 | ``` 47 | 48 | You will see in the example script that we are using the `/autoexecution/search/transaction?transactionId=${transactionId}` Overledger URL to search for the given transactionId. 49 | 50 | This script is the same as in [Exercise 3](./Exercise3.md). Recall that this script firstly gets the latest block, then if the block is not empty it will ask Overledger for the last transaction in the block. It gets the last transaction as transactions in a block are processed in order. Should the last transaction in the block not be a payment one, then the script will ask Overledger for the previous transaction in the block, and so on until a payment transaction is found. 51 | 52 | Note that in the foundations course, you don't have to concern yourself with the other transaction types, but they will be covered in a future course. 53 | 54 | All the logic in this script is based on the Overledger standardised data model. This means that the script can easily be reused for other DLTs that are UTXO or Accounts based. 55 | 56 | ##### Searching for a Specific Transaction 57 | 58 | Take a look at a third party explorer for the DLT testnets we are using, e.g. [the Ethereum Goerli Testnet](https://goerli.etherscan.io/) or [the XRP Ledger Testnet](https://blockexplorer.one/xrp/testnet). 59 | 60 | Choose a transaction from a block in these explorers. Can you understand how to modify the example script to search for your chosen transactions (use your transaction IDs)? 61 | 62 | #### Troubleshooting 63 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 64 | 65 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 66 | 67 | #### Error: bad decrypt 68 | 69 | Description: 70 | 71 | ``` 72 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 73 | ``` 74 | 75 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 76 | 77 | Solution: provide the password with which .env.enc was encrypted when running the script. 78 | 79 | #### Error: .env.enc does not exist 80 | 81 | Description: 82 | 83 | ``` 84 | Secure-env : ERROR OCCURED .env.enc does not exist. 85 | ``` 86 | 87 | Cause: You are missing the encrypted environment file in the folder that you are running from. 88 | 89 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 90 | 91 | #### Error: Missing Password 92 | 93 | Description: 94 | 95 | ``` 96 | Error: Please insert a password to decrypt the secure env file. 97 | ``` 98 | 99 | Cause: You did not include the password as a command line option. 100 | 101 | Solution: Include the password as a command line option as stated in your terminal print out. -------------------------------------------------------------------------------- /Exercise6.md: -------------------------------------------------------------------------------- 1 | # Week 2 Ledger State and Updates 2 | 3 | ## The Accounts Model 4 | 5 | ### Exercise - Read your first Accounts State 6 | 7 | In this exercise, we will read our first Accounts state via Overledger’s autoExecuteSearchAddressBalance and autoExecuteSearchAddressSequence APIs. The documentation for these endpoints can be found [here](https://docs.overledger.io/#operation/autoExecuteSearchAddressBalanceRequest) and [here](https://docs.overledger.io/#operation/prepareAddressSequenceSearchRequest_1) respectively. 8 | 9 | Note that unlike blocks and transactions, the state data model of utxo and accounts based DLTs do have to diverge somewhat. This is because of the wide variety of parameters in the state of both models. 10 | 11 | #### DLT Network Information 12 | 13 | We will be interacting with the Ethereum Goerli and the XRP Ledger testnets. The relevant Overledger location objects are as follows: 14 | 15 | 1. `Location = {“technology”: “Ethereum”, “network”: “Ethereum Goerli Testnet”}` 16 | 17 | 2. `Location = {“technology”: “XRP Ledger”, “network”: “Testnet”}` 18 | 19 | #### Prerequisites 20 | 21 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md) and that you have completed the previous exercises to search for a block using Overledger [here](./Exercise2.md) and to search for an Account transaction using Overledger [here](./Exercise5.md). 22 | 23 | #### Searching Accounts for Specific Properties 24 | 25 | We will demostrate searching the Accounts state through a specific example. This example will search a subset of addresses and identify the address with the largest balance and the address with the largest sequence number. To be in the subset of addresses searched, an address will have had to send a transaction in the latest block of the Ethereum Goerli testnet. To run the example, enter: 26 | 27 | ``` 28 | node examples/state-search/autoexecute-accounts-search.js password=MY_PASSWORD 29 | ``` 30 | 31 | You will see in the example script that we are using the `/autoexecution/search/address/balance/${originId}` Overledger URL to search for an address balance and we are using the `/autoexecution/search/address/sequence/${originId}` Overledger URL to search for an address sequence. 32 | 33 | The full details of this script is as follows. This script first gets the latest block, then gets each transaction from the block. For each transaction origin address, the script gets that addresses balance and sequence number. The script keeps track of the address with the largest parameters. 34 | 35 | All the logic in this script is based on the Overledger standardised data model. This means that the script can easily be reused for other DLTs that are Account based. 36 | 37 | ##### Overledger Auto Execute Balance Search API Response 38 | 39 | See that the response has two main objects due to Overledger’s preparation and execution model: 40 | 41 | 1. preparationAddressBalanceSearchResponse: This includes the request id and any QNT fee that must be paid for use of this endpoint. 42 | 43 | 2. executionAddressBalanceSearchResponse: This includes the requested address balance. 44 | 45 | The balance information will be returned in cross-DLT standardised form for the account data model. There is no associated status object as balances are read from the state of the latest block. 46 | 47 | For parameter by parameter descriptions see the [openAPI3 doc](https://docs.overledger.io/#operation/autoExecuteSearchAddressBalanceRequest). 48 | 49 | ##### Overledger Auto Execute Sequence Search API Response 50 | 51 | See that the response has two main objects due to Overledger’s preparation and execution model: 52 | 53 | 1. preparationAddressSequenceSearchResponse: This includes the request id and any QNT fee that must be paid for use of this endpoint. 54 | 55 | 2. executionAddressSequenceSearchResponse: This includes the requested address sequence. 56 | 57 | The sequence information will be returned in cross-DLT standardised form for the account data model. There is no associated status object as sequences are read from the state of the latest block. 58 | 59 | For parameter by parameter descriptions see the [documentation](https://docs.overledger.io/#operation/prepareAddressSequenceSearchRequest_1). 60 | 61 | #### Challenges 62 | 63 | ##### Searching the XRP Ledger Testnet 64 | 65 | Given the example `./examples/state-search/autoexecute-accounts-search.js` file and the location information listed above, can you understand how to change this file to instead run for the XRP Ledger testnet? 66 | 67 | ##### Searching for a Specific Address 68 | 69 | Take a look at a third party explorer for the DLT testnets we are using, e.g. [the Ethereum Goerli Testnet](https://goerli.etherscan.io/) or [the XRP Ledger Testnet](https://blockexplorer.one/xrp/testnet). 70 | 71 | Choose any account address from these explorers. Can you understand how to modify the example script to search for that account's balance and sequence? 72 | 73 | #### Troubleshooting 74 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 75 | 76 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 77 | 78 | #### Error: bad decrypt 79 | 80 | Description: 81 | 82 | ``` 83 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 84 | ``` 85 | 86 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 87 | 88 | Solution: provide the password with which .env.enc was encrypted when running the script. 89 | 90 | #### Error: .env.enc does not exist 91 | 92 | Description: 93 | 94 | ``` 95 | Secure-env : ERROR OCCURED .env.enc does not exist. 96 | ``` 97 | 98 | Cause: You are missing the encrypted environment file in the folder that you are running from. 99 | 100 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 101 | 102 | #### Error: Missing Password 103 | 104 | Description: 105 | 106 | ``` 107 | Error: Please insert a password to decrypt the secure env file. 108 | ``` 109 | 110 | Cause: You did not include the password as a command line option. 111 | 112 | Solution: Include the password as a command line option as stated in your terminal print out. -------------------------------------------------------------------------------- /Exercise3.md: -------------------------------------------------------------------------------- 1 | # Week 2 Ledger State and Updates 2 | 3 | ## The Unspent Transaction Output Model 4 | 5 | ### Exercise - Read your first UTXO Transaction 6 | 7 | In this exercise, we will read our first UTXO transaction via Overledger’s autoExecuteSearchTransaction API. The documentation for this endpoint can be found [here](https://docs.overledger.io/#operation/autoExecuteSearchTransactionRequest). 8 | 9 | #### DLT Network Information 10 | 11 | We will be interacting with the Bitcoin testnet. The relevant Overledger location object is as follows: 12 | 13 | ``Location = {“technology”: “Bitcoin”, “network”: “Testnet”}`` 14 | 15 | #### Prerequisites 16 | 17 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md) and that you have completed the previous exercise to search for a block using Overledger [here](./Exercise2.md). 18 | 19 | #### Searching for the Latest Payment Transaction 20 | 21 | We will demostrate searching for a UTXO transaction through a specific example. In this example we will search for the latest payment transaction on the Bitcoin test network. To do so, run the following script: 22 | 23 | ``` 24 | node examples/transaction-search/autoexecute-transaction-search.js password=MY_PASSWORD 25 | ``` 26 | 27 | You will see in the example script that we are using the `/autoexecution/search/transaction?transactionId=${transactionId}` Overledger URL to search for the given transactionId. 28 | 29 | The full details of this script is as follows. Firstly it gets the latest block, then if the block is not empty it will ask Overledger for the last transaction in the block. It gets the last transaction as transactions in a block are processed in order. Should the last transaction in the block not be a payment one, then the script will ask Overledger for the previous transaction in the block, and so on until a payment transaction is found. 30 | 31 | Note that in the foundations course, you don't have to concern yourself with the other transaction types, but they will be covered in a future course. 32 | 33 | All the logic in this script is based on the Overledger standardised data model. This means that the script can easily be reused for other DLTs that are UTXO or Accounts based. All that is required is to change the location object to another network. 34 | 35 | ##### Overledger Auto Execute Transaction Search API Response 36 | 37 | See that the response has two main objects due to Overledger’s preparation and execution model: 38 | 39 | 1. PreparationTransactionSearchResponse: This includes the request id and any QNT fee that must be paid for use of this endpoint. 40 | 2. ExecutionTransactionSearchResponse: This includes information on the requested transaction and related metadata. 41 | 42 | The transaction information will be returned in cross-DLT standardised form and in the DLT specific form (referred to as nativeData). This allows maximum flexibility to software developers, as they can choose to use either data models. 43 | 44 | ##### Auto Execute Transaction Search API Response Main Components 45 | 46 | The transaction response will contain a few main components. Notice that the metadata components (location and status) are the same for a transaction as they are with a block. 47 | 48 | 1. Location: Each Overledger DLT data response includes a reference to the location (technology, network) of where this data was taken from. This helps with auditing. 49 | 50 | 2. Status: Overledger responses regarding blocks and transactions come with a status. Due to some DLTs having probabilistic finality of transactions/blocks and other DLTs having deterministic finality of transaction/blocks, the status object is used to indicate to the developer when the requested data is assumed to be final (therefore status.value = “SUCCESSFUL”) or not (therefore status.value=“PENDING”). 51 | 52 | 3. Transaction = The requested transaction data in standardised and nativeData formats. 53 | 54 | For parameter by parameter descriptions see the [documentation](https://docs.overledger.io/#operation/autoExecuteSearchTransactionRequest). 55 | 56 | ###### Auto Execute Transaction Search API Response Origins and Destinations 57 | 58 | In the UTXO model, a transaction contains one or more origins (inputs) and one or more destinations (outputs). In the UTXO model the origin and destination identifiers have specific meanings: 59 | 60 | - OriginId: This is a reference to the transactionId and DestinationArrayIndex of an unspent transaction output that is now being spent. 61 | - DestinationId: This is a reference to an externally owned account (controlled by a private key) or to a smart contract address (controlled by smart contract code). An externally owned account requires a signature to spent the BTC associated to this transaction output. Whereas a smart contract address requires some user defined parameters to be satisfied in order for the transaction output to be spent. 62 | 63 | #### Challenges 64 | 65 | ##### Searching for a Specific Transaction 66 | 67 | Take a look at a third party explorer for the Bitcoin testnet we are using, e.g. [here](https://blockstream.info/testnet/). 68 | 69 | Choose a transaction from a block in this explorer. Can you understand how to modify the example script to search for your chosen transaction? 70 | 71 | #### Troubleshooting 72 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 73 | 74 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 75 | 76 | #### Error: bad decrypt 77 | 78 | Description: 79 | 80 | ``` 81 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 82 | ``` 83 | 84 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 85 | 86 | Solution: provide the password with which .env.enc was encrypted when running the script. 87 | 88 | #### Error: .env.enc does not exist 89 | 90 | Description: 91 | 92 | ``` 93 | Secure-env : ERROR OCCURED .env.enc does not exist. 94 | ``` 95 | 96 | Cause: You are missing the encrypted environment file in the folder that you are running from. 97 | 98 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 99 | 100 | #### Error: Missing Password 101 | 102 | Description: 103 | 104 | ``` 105 | Error: Please insert a password to decrypt the secure env file. 106 | ``` 107 | 108 | Cause: You did not include the password as a command line option. 109 | 110 | Solution: Include the password as a command line option as stated in your terminal print out. -------------------------------------------------------------------------------- /examples/transaction-search/autoexecute-transaction-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "transaction-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/transaction-search/autoexecute-transaction-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/transaction-search/autoexecute-transaction-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | log.info("Executing ", courseModule); 37 | (async () => { 38 | try { 39 | log.info("Initializing the SDK"); 40 | const overledger = new OverledgerSDK({ 41 | dlts: [ 42 | { dlt: DltNameOptions.BITCOIN }, 43 | { dlt: DltNameOptions.ETHEREUM }, 44 | { dlt: DltNameOptions.XRP_LEDGER }, 45 | ], // connects OVL to these 3 technologies 46 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 47 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 48 | envFilePassword: SENV_PASSWORD, 49 | }); 50 | 51 | log.info("Obtaining the Access Token to Interact with Overledger"); 52 | const refreshTokensResponse = 53 | await overledger.getTokensUsingClientIdAndSecret( 54 | process.env.USER_NAME, 55 | process.env.PASSWORD, 56 | process.env.CLIENT_ID, 57 | process.env.CLIENT_SECRET, 58 | ); 59 | 60 | log.info( 61 | "Creating the Overledger Request Object with the Correct Location", 62 | ); 63 | const overledgerRequestMetaData = { 64 | location: { 65 | technology: "Bitcoin", 66 | network: "Testnet", 67 | }, 68 | }; 69 | const overledgerInstance = overledger.provider.createRequest( 70 | refreshTokensResponse.accessToken.toString(), 71 | ); 72 | 73 | log.info("Locating the Latest Payment Transaction on the Blockchain"); 74 | let locatedPaymentTransaction = false; 75 | let numberOfTransactionsInBlock; 76 | let transactionsInBlockCounter; 77 | let transactionId; 78 | let overledgerTransactionResponse; 79 | let overledgerBlockResponse; 80 | let blockToSearch = "latest"; 81 | 82 | while (locatedPaymentTransaction === false) { 83 | log.info(`Asking Overledger for Block: ${blockToSearch}`); 84 | overledgerBlockResponse = await overledgerInstance.post( 85 | `/autoexecution/search/block/${blockToSearch}`, 86 | overledgerRequestMetaData, 87 | ); 88 | 89 | transactionsInBlockCounter = 90 | overledgerBlockResponse.data.executionBlockSearchResponse.block 91 | .numberOfTransactions - 1; 92 | log.info( 93 | `Transactions in Block = ${overledgerBlockResponse.data.executionBlockSearchResponse.block.numberOfTransactions}`, 94 | ); 95 | // check if there is any transactions in this block 96 | while (transactionsInBlockCounter < 0) { 97 | // if there is no transactions then ... 98 | log.info( 99 | `Block Number ${overledgerBlockResponse.data.executionBlockSearchResponse.block.number} does not have any transactions`, 100 | ); 101 | log.info("Therefore searching for previous block..."); 102 | blockToSearch = 103 | overledgerBlockResponse.data.executionBlockSearchResponse.block 104 | .number - 1; 105 | overledgerBlockResponse = await overledgerInstance.post( 106 | `/autoexecution/search/block/${blockToSearch}`, 107 | overledgerRequestMetaData, 108 | ); 109 | transactionsInBlockCounter = 110 | overledgerBlockResponse.data.executionBlockSearchResponse.block 111 | .numberOfTransactions - 1; 112 | } 113 | log.info( 114 | `Block number ${overledgerBlockResponse.data.executionBlockSearchResponse.block.number} includes transactions`, 115 | ); 116 | log.info( 117 | `Transactions in Block = ${overledgerBlockResponse.data.executionBlockSearchResponse.block.numberOfTransactions}`, 118 | ); 119 | // start from the last transaction of the block (as blockchains process from transaction 1 of the block to transaction n) 120 | log.info( 121 | `Payment Transaction Search Will Start From Transaction Number: ${transactionsInBlockCounter}`, 122 | ); 123 | 124 | while (transactionsInBlockCounter >= 0) { 125 | // get n'th transaction id 126 | log.info( 127 | `Asking Overledger for Transaction ${transactionsInBlockCounter} in Block ${blockToSearch}`, 128 | ); 129 | transactionId = 130 | overledgerBlockResponse.data.executionBlockSearchResponse.block 131 | .transactionIds[transactionsInBlockCounter]; 132 | log.info(`The Id of this Transaction is ${transactionId}`); 133 | // query Overledger for this transaction 134 | overledgerTransactionResponse = await overledgerInstance.post( 135 | `/autoexecution/search/transaction?transactionId=${transactionId}`, 136 | overledgerRequestMetaData, 137 | ); 138 | log.info( 139 | `The Type of this Transaction is ${overledgerTransactionResponse.data.executionTransactionSearchResponse.type}`, 140 | ); 141 | if ( 142 | overledgerTransactionResponse.data.executionTransactionSearchResponse 143 | .type === "PAYMENT" 144 | ) { 145 | transactionsInBlockCounter = numberOfTransactionsInBlock; 146 | locatedPaymentTransaction = true; 147 | } else { 148 | transactionsInBlockCounter -= 1; 149 | } 150 | } 151 | 152 | if (locatedPaymentTransaction === false) { 153 | log.info( 154 | `No Payment Transactions Found in Block Number ${overledgerBlockResponse.data.executionBlockSearchResponse.block.number}`, 155 | ); 156 | blockToSearch = 157 | overledgerBlockResponse.data.executionBlockSearchResponse.block 158 | .number - 1; 159 | } 160 | } 161 | 162 | log.info( 163 | `Printing Out Overledger's Response:\n\n${JSON.stringify( 164 | overledgerTransactionResponse.data, 165 | )}\n\n`, 166 | ); 167 | } catch (e) { 168 | log.error("error", e); 169 | } 170 | })(); 171 | -------------------------------------------------------------------------------- /examples/state-search/autoexecute-utxo-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "utxo-state-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password for the secure env 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/state-search/autoexecute-utxo-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/state-search/autoexecute-utxo-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | log.info("Executing ", courseModule); 37 | (async () => { 38 | try { 39 | log.info("Initializing the SDK"); 40 | const overledger = new OverledgerSDK({ 41 | dlts: [ 42 | { dlt: DltNameOptions.BITCOIN }, 43 | { dlt: DltNameOptions.ETHEREUM }, 44 | { dlt: DltNameOptions.XRP_LEDGER }, 45 | ], // connects OVL to these 3 technologies 46 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 47 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 48 | envFilePassword: SENV_PASSWORD, 49 | }); 50 | 51 | log.info("Obtaining the Access Token to Interact with Overledger"); 52 | const refreshTokensResponse = 53 | await overledger.getTokensUsingClientIdAndSecret( 54 | process.env.USER_NAME, 55 | process.env.PASSWORD, 56 | process.env.CLIENT_ID, 57 | process.env.CLIENT_SECRET, 58 | ); 59 | 60 | log.info( 61 | "Creating the Overledger Request Object with the Correct Location", 62 | ); 63 | const overledgerRequestMetaData = { 64 | location: { 65 | technology: "Bitcoin", 66 | network: "Testnet", 67 | }, 68 | }; 69 | const overledgerInstance = overledger.provider.createRequest( 70 | refreshTokensResponse.accessToken.toString(), 71 | ); 72 | 73 | log.info("Locating the Largest Unspent UTXO in a recent Block"); 74 | let transactionId; 75 | let overledgerTransactionResponse; 76 | let overledgerUTXOResponse; 77 | let utxoCount; 78 | let utxoId; 79 | let thisUtxoAmount; 80 | let maxUtxoAmount = 0; 81 | let maxUtxoId; 82 | let maxUtxoDestination; 83 | let overledgerUTXOMaxBalanceResponse; 84 | let utxoStatus; 85 | 86 | log.info(`Asking Overledger for the Latest Block`); 87 | let overledgerBlockResponse = await overledgerInstance.post( 88 | `/autoexecution/search/block/latest`, 89 | overledgerRequestMetaData, 90 | ); 91 | const blockNumber = 92 | overledgerBlockResponse.data.executionBlockSearchResponse.block.number - 93 | 20; 94 | log.info(`Asking Overledger for the Block 20 Back from the Latest`); 95 | overledgerBlockResponse = await overledgerInstance.post( 96 | `/autoexecution/search/block/${blockNumber}`, 97 | overledgerRequestMetaData, 98 | ); 99 | const transactionsInBlock = 100 | overledgerBlockResponse.data.executionBlockSearchResponse.block 101 | .numberOfTransactions - 1; 102 | log.info( 103 | `Transactions in Block = ${overledgerBlockResponse.data.executionBlockSearchResponse.block.numberOfTransactions}`, 104 | ); 105 | 106 | // check if there is any transactions in this block 107 | if (transactionsInBlock < 0) { 108 | log.info(`The latest block has no transactions. Please try again later`); 109 | } else { 110 | let counter = 0; 111 | while (counter <= transactionsInBlock) { 112 | // get n'th transaction id 113 | log.info( 114 | `Asking Overledger for Transaction ${counter} in Block ${blockNumber}`, 115 | ); 116 | transactionId = 117 | overledgerBlockResponse.data.executionBlockSearchResponse.block 118 | .transactionIds[counter]; 119 | log.info(`The Id of this Transaction is ${transactionId}`); 120 | // query Overledger for this transaction 121 | overledgerTransactionResponse = await overledgerInstance.post( 122 | `/autoexecution/search/transaction?transactionId=${transactionId}`, 123 | overledgerRequestMetaData, 124 | ); 125 | utxoCount = 126 | overledgerTransactionResponse.data.executionTransactionSearchResponse 127 | .transaction.destination.length - 1; 128 | log.info( 129 | `This Transaction has ${overledgerTransactionResponse.data.executionTransactionSearchResponse.transaction.destination.length} destinations`, 130 | ); 131 | while (utxoCount >= 0) { 132 | utxoId = `${transactionId}:${utxoCount.toString()}`; 133 | log.info( 134 | `Asking Overledger for UTXO ${utxoCount} in Transaction ${counter}`, 135 | ); 136 | overledgerUTXOResponse = await overledgerInstance.post( 137 | `/autoexecution/search/utxo/${utxoId}`, 138 | overledgerRequestMetaData, 139 | ); 140 | utxoStatus = 141 | overledgerUTXOResponse.data.executionUtxoSearchResponse.status.code; 142 | log.info(`The UTXO has a status of ${utxoStatus}`); 143 | if (utxoStatus === "UNSPENT_SUCCESSFUL") { 144 | thisUtxoAmount = 145 | overledgerUTXOResponse.data.executionUtxoSearchResponse 146 | .destination[0].payment.amount; 147 | if (thisUtxoAmount > maxUtxoAmount) { 148 | maxUtxoAmount = thisUtxoAmount; 149 | maxUtxoId = utxoId; 150 | maxUtxoDestination = 151 | overledgerUTXOResponse.data.executionUtxoSearchResponse 152 | .destination[0].destinationId; 153 | overledgerUTXOMaxBalanceResponse = overledgerUTXOResponse; 154 | } 155 | } 156 | utxoCount -= 1; 157 | } 158 | counter += 1; 159 | } 160 | 161 | const balanceUnit = 162 | overledgerUTXOResponse.data.executionUtxoSearchResponse.destination[0] 163 | .payment.unit; 164 | log.info(); 165 | log.info(`In Block ${blockNumber}:`); 166 | log.info(`The Largest UTXO is: ${maxUtxoId}`); 167 | log.info(`The Address with the Largest UTXO is: ${maxUtxoDestination}`); 168 | log.info(`This UTXO has locked: ${maxUtxoAmount} ${balanceUnit}`); 169 | 170 | log.info( 171 | `Overledger's Response For the Max UTXO Was:\n\n${JSON.stringify( 172 | overledgerUTXOMaxBalanceResponse.data, 173 | )}\n\n`, 174 | ); 175 | } 176 | } catch (e) { 177 | log.error("error", e); 178 | } 179 | })(); 180 | -------------------------------------------------------------------------------- /Exercise4.md: -------------------------------------------------------------------------------- 1 | # Week 2 Ledger State and Updates 2 | 3 | ## The Unspent Transaction Output Model 4 | 5 | ### Exercise - Read your first UTXO State 6 | 7 | In this exercise, we will read our first UTXO state via Overledger’s autoExecuteSearchUtxo API. The documentation for this endpoint can be found [here](https://docs.overledger.io/#operation/autoExecuteSearchUtxoRequest). 8 | 9 | Note that unlike blocks and transactions, the state data model of utxo and accounts based DLTs do have to diverge somewhat. This is because of the wide variety of parameters in the state of both models. 10 | 11 | #### DLT Network Information 12 | 13 | We will be interacting with the Bitcoin testnet. The relevant Overledger location object is as follows: 14 | 15 | ``Location = {“technology”: “Bitcoin”, “network”: “Testnet”}`` 16 | 17 | #### Prerequisites 18 | 19 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md) and that you have completed the previous exercises to search for a block using Overledger [here](./Exercise2.md) and to search for a UTXO transaction using Overledger [here](./Exercise3.md). 20 | 21 | #### Searching for the Largest Unspent UTXO in a block 22 | 23 | We will demostrate searching the UTXO state through a specific example. This example will search for the largest Unspent UTXO in a recent block of the Bitcoin test network. To run the example, enter: 24 | 25 | ``` 26 | node examples/state-search/autoexecute-utxo-search.js password=MY_PASSWORD 27 | ``` 28 | 29 | You will see in the example script that we are using the `/autoexecution/search/utxo/${utxoId}` Overledger URL to search for the given utxoId. 30 | 31 | The full details of this script is as follows. Firstly it gets the latest block, then requests the block that is 20 block's back from the current latest block. We have choosen this particular block, so that hopefully it will contain a variety of UTXO statuses (see below for the list of possible statuses). If the chosen block is empty, the script will complete. Otherwise it will ask Overledger for information on each transaction in the block. The script will then get information from Overledger on each transaction destination. The script checks if each related output is currently unspent or not by utilising Overledger's standardised UTXO status codes, which are as follows: 32 | 33 | - UNSPENT_PENDING: This transaction output has been created in a transaction that Overledger currently deems not final (hence the PENDING suffix). This transaction has not yet been spent in any other transaction (hence the UNSPENT prefix). 34 | - UNSPENT_SUCCESSFUL: This transaction output has been created in a transaction that Overledger deems final (hence the SUCCESSFUL suffix). This transaction has not yet been spent in any other transaction (hence the UNSPENT prefix). 35 | - SPENT: This transaction has been spent in another transaction. 36 | - UNSPENDABLE_PENDING: This transaction output has been created in a transaction that Overledger currently deems not final (hence the PENDING suffix). But this transaction output can never be spent, because it has no unlocking condition (hence the UNSPENDABLE prefix). 37 | - UNSPENDABLE_SUCCESSFUL: This transaction output has been created in a transaction that Overledger deems final (hence the SUCCESSFUL suffix). But this transaction output can never be spent, because it has no unlocking condition (hence the UNSPENDABLE prefix). 38 | 39 | All the logic in this script is based on the Overledger standardised data model. This means that the script can easily be reused for other DLTs that are UTXO based. All that is required is to change the location object to another UTXO based DLT network. 40 | 41 | ##### Overledger Auto Execute UTXO Search API Response 42 | 43 | See that the response has two main objects due to Overledger’s preparation and execution model: 44 | 45 | 1. preparationUtxoSearchResponse: This includes the request id and any QNT fee that must be paid for use of this endpoint. 46 | 2. executionUtxoSearchResponse: This includes information on the requested transaction destination and related metadata. 47 | 48 | The utxo information will be returned in cross-DLT standardised form for the utxo data model and in the DLT specific form (referred to as nativeData). This allows maximum flexibility to software developers, as they can choose to use either data models. 49 | 50 | ##### Auto Execute UTXO Search API Response Main Components 51 | 52 | The UTXO response will contain a few main components. Notice that the metadata components (location and status) are the same for a UTXO as they are for a transaction and block. The only difference is the status codes, as described in the section above. 53 | 54 | 1. Location: Each Overledger DLT data response includes a reference to the location (technology, network) of where this data was taken from. This helps with auditing. 55 | 56 | 2. Status: Overledger responses regarding utxos come with a status. Due to some DLTs having probabilistic finality of transactions/blocks and other DLTs having deterministic finality of transaction/blocks, the status object is used to indicate to the developer when the requested data is assumed to be final (therefore status.value has the suffix “SUCCESSFUL”) or not (therefore status.value has the suffix “PENDING”). As transaction outputs can have an additional three statuses ("SPENT", "UNSPENT", "UNSPENDABLE", as described above), this part of the status is included as the prefix. 57 | 58 | 3. Destination = The requested transaction output, presented as the Overledger standardised destination and it's relevant nativeData formats. 59 | 60 | For parameter by parameter descriptions see the [documentation](https://docs.overledger.io/#operation/autoExecuteSearchUtxoRequest). 61 | 62 | #### Challenges 63 | 64 | ##### Searching for a Specific Transaction Output 65 | 66 | Take a look at a third party explorer for the Bitcoin testnet we are using, e.g. [here](https://blockstream.info/testnet/). 67 | 68 | Choose a transaction from a block in this explorer. Then choose a specific transaction output. Can you understand how to modify the example script to search for your chosen transaction output? 69 | 70 | #### Troubleshooting 71 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 72 | 73 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 74 | 75 | #### Error: bad decrypt 76 | 77 | Description: 78 | 79 | ``` 80 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 81 | ``` 82 | 83 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 84 | 85 | Solution: provide the password with which .env.enc was encrypted when running the script. 86 | 87 | #### Error: .env.enc does not exist 88 | 89 | Description: 90 | 91 | ``` 92 | Secure-env : ERROR OCCURED .env.enc does not exist. 93 | ``` 94 | 95 | Cause: You are missing the encrypted environment file in the folder that you are running from. 96 | 97 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 98 | 99 | #### Error: Missing Password 100 | 101 | Description: 102 | 103 | ``` 104 | Error: Please insert a password to decrypt the secure env file. 105 | ``` 106 | 107 | Cause: You did not include the password as a command line option. 108 | 109 | Solution: Include the password as a command line option as stated in your terminal print out. -------------------------------------------------------------------------------- /examples/state-search/autoexecute-accounts-search.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "accounts-state-search"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const PASSWORD_INPUT = process.argv.slice(2).toString(); 25 | const SENV_PASSWORD = PASSWORD_INPUT.split("=")[1]; 26 | 27 | // Check for provided password for the secure env 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/state-search/autoexecute-accounts-search.js password=MY_PASSWORD", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/state-search/autoexecute-accounts-search.js password=MY_PASSWORD", 34 | ); 35 | } 36 | log.info("Executing ", courseModule); 37 | (async () => { 38 | try { 39 | log.info("Initializing the SDK"); 40 | const overledger = new OverledgerSDK({ 41 | dlts: [ 42 | { dlt: DltNameOptions.BITCOIN }, 43 | { dlt: DltNameOptions.ETHEREUM }, 44 | { dlt: DltNameOptions.XRP_LEDGER }, 45 | ], // connects OVL to these 3 technologies 46 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 47 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 48 | envFilePassword: SENV_PASSWORD, 49 | }); 50 | 51 | log.info("Obtaining the Access Token to Interact with Overledger"); 52 | const refreshTokensResponse = 53 | await overledger.getTokensUsingClientIdAndSecret( 54 | process.env.USER_NAME, 55 | process.env.PASSWORD, 56 | process.env.CLIENT_ID, 57 | process.env.CLIENT_SECRET, 58 | ); 59 | 60 | log.info( 61 | "Creating the Overledger Request Object with the Correct Location", 62 | ); 63 | const overledgerRequestMetaData = { 64 | location: { 65 | technology: "Ethereum", 66 | network: "Ethereum Goerli Testnet", 67 | }, 68 | }; 69 | const overledgerInstance = overledger.provider.createRequest( 70 | refreshTokensResponse.accessToken.toString(), 71 | ); 72 | 73 | log.info( 74 | "Locating the Largest Balance and Sequence Number of any Account Who Sent a Transaction in the Latest Block", 75 | ); 76 | let transactionId; 77 | let originId; 78 | let thisBalance; 79 | let thisSequence; 80 | let maxBalance = 0; 81 | let maxSequence = 0; 82 | let originIdWithMaxBalance; 83 | let originIdWithMaxSequence; 84 | let overledgerTransactionResponse; 85 | let overledgerAddressBalanceResponse; 86 | let overledgerAddressSequenceResponse; 87 | let overledgerAddressMaxBalanceResponse; 88 | let overledgerAddressMaxSequenceResponse; 89 | 90 | log.info(`Asking Overledger for the latest Block`); 91 | const overledgerBlockResponse = await overledgerInstance.post( 92 | `/autoexecution/search/block/latest`, 93 | overledgerRequestMetaData, 94 | ); 95 | const transactionsInBlock = 96 | overledgerBlockResponse.data.executionBlockSearchResponse.block 97 | .numberOfTransactions - 1; 98 | const blockNumber = 99 | overledgerBlockResponse.data.executionBlockSearchResponse.block.number; 100 | log.info( 101 | `Transactions in Block = ${overledgerBlockResponse.data.executionBlockSearchResponse.block.numberOfTransactions}`, 102 | ); 103 | 104 | // check if there is any transactions in this block 105 | if (transactionsInBlock < 0) { 106 | log.info(`The latest block has no transactions. Please try again later.`); 107 | } else { 108 | let counter = 0; 109 | while (counter <= transactionsInBlock) { 110 | // get n'th transaction id 111 | log.info( 112 | `Asking Overledger for Transaction ${counter} in Block ${blockNumber}`, 113 | ); 114 | transactionId = 115 | overledgerBlockResponse.data.executionBlockSearchResponse.block 116 | .transactionIds[counter]; 117 | log.info(`The Id of this Transaction is ${transactionId}`); 118 | // query Overledger for this transaction 119 | overledgerTransactionResponse = await overledgerInstance.post( 120 | `/autoexecution/search/transaction?transactionId=${transactionId}`, 121 | overledgerRequestMetaData, 122 | ); 123 | originId = 124 | overledgerTransactionResponse.data.executionTransactionSearchResponse 125 | .transaction.origin[0].originId; 126 | log.info(`The originId of this Transaction is ${originId}`); 127 | // query Overledger for the origin's balance 128 | overledgerAddressBalanceResponse = await overledgerInstance.post( 129 | `/autoexecution/search/address/balance/${originId}`, 130 | overledgerRequestMetaData, 131 | ); 132 | thisBalance = 133 | overledgerAddressBalanceResponse.data 134 | .executionAddressBalanceSearchResponse.balances[0].amount; 135 | // query Overledger for the origin's sequence 136 | overledgerAddressSequenceResponse = await overledgerInstance.post( 137 | `/autoexecution/search/address/sequence/${originId}`, 138 | overledgerRequestMetaData, 139 | ); 140 | thisSequence = 141 | overledgerAddressSequenceResponse.data 142 | .executionAddressSequenceSearchResponse.sequence; 143 | 144 | // change the maximums if required 145 | if (thisBalance > maxBalance) { 146 | maxBalance = thisBalance; 147 | originIdWithMaxBalance = originId; 148 | overledgerAddressMaxBalanceResponse = 149 | overledgerAddressBalanceResponse; 150 | } 151 | if (thisSequence > maxSequence) { 152 | maxSequence = thisSequence; 153 | originIdWithMaxSequence = originId; 154 | overledgerAddressMaxSequenceResponse = 155 | overledgerAddressSequenceResponse; 156 | } 157 | 158 | counter++; 159 | } 160 | 161 | const balanceUnit = 162 | overledgerAddressBalanceResponse.data 163 | .executionAddressBalanceSearchResponse.balances[0].unit; 164 | log.info(); 165 | log.info(`In Block ${blockNumber}:`); 166 | log.info( 167 | `The Address with the Largest Balance is: ${originIdWithMaxBalance}`, 168 | ); 169 | log.info(`This Address had a Balance of: ${maxBalance} ${balanceUnit}`); 170 | log.info( 171 | `The Address with the Largest Sequence is: ${originIdWithMaxSequence}`, 172 | ); 173 | log.info(`This Address had a Sequence Number of: ${maxSequence}`); 174 | 175 | log.info( 176 | `Overledger's Response For the Max Balance Was:\n\n${JSON.stringify( 177 | overledgerAddressMaxBalanceResponse.data, 178 | )}\n\n`, 179 | ); 180 | 181 | log.info( 182 | `Overledger's Response For the Max Sequence Was:\n\n${JSON.stringify( 183 | overledgerAddressMaxSequenceResponse.data, 184 | )}\n\n`, 185 | ); 186 | } 187 | } catch (e) { 188 | log.error("error", e); 189 | } 190 | })(); 191 | -------------------------------------------------------------------------------- /Exercise2.md: -------------------------------------------------------------------------------- 1 | # Week 1 Introduction to Distributed Ledger Technologies 2 | 3 | ## Distributed Ledger Technologies 4 | 5 | ### Exercise - Read Data From Distributed Ledgers 6 | 7 | In this exercise, we will be reading blocks via Overledger’s *autoExecuteSearchBlock* API. The documentation for this endpoint can be found [here](https://docs.overledger.io/tag/Block-Search#operation/autoExecuteSearchBlockRequest). 8 | 9 | Recall that Overledger allows two forms of interaction: 10 | 11 | - Preparation and Execution: One API call to prepare the request. Another API call to confirm the request. 12 | - AutoExecute: One API call to prepare and confirm the request. 13 | 14 | Overledger requires a preparation phase as Overledger’s API data model is standardised across different DLTs, and so the preparation allows a mapping to occur between the Overledger data model and the native data model of each DLT. 15 | 16 | The preparation phase can be split from the execution phase, if the user wants to check these mappings have occurred correctly. These split phases also allow a client side audit trail of the mappings to be saved. 17 | 18 | For all of the read examples in the course we will use the AutoExecute interaction type. 19 | 20 | #### DLT Network Information 21 | 22 | We will be interacting with the Bitcoin, Ethereum & XRP Ledger testnets. Each network has been designated a location so that Overledger can route requests for these DLT networks correctly. These locations are as follows: 23 | 24 | 1. Bitcoin 25 | `Location = {“technology”: “Bitcoin”, “network”: “Testnet”}` 26 | 27 | 2. Ethereum 28 | `Location = {“technology”: “Ethereum”, “network”: “Ethereum Goerli Testnet”}` 29 | 30 | 3. XRP 31 | `Location = {“technology”: “XRP Ledger”, “network”: “Testnet”}` 32 | 33 | Note that Ethereum has a named test network (Goerli) above as Ethereum has multiple test networks (e.g. Goerli, Sepolia, Ropsten, Rinkeby, Kovan etc). Therefore the additional name differentiates one test network from another. 34 | 35 | #### Prerequisites 36 | 37 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md). 38 | 39 | #### Searching for the Latest Block 40 | 41 | We will start by searching for the latest block on the Bitcoin DLT network. To do so, run the following script: 42 | 43 | ``` 44 | node examples/block-search/autoexecute-latest-block-search.js password=MY_PASSWORD 45 | ``` 46 | 47 | You will see in the example script (referenced above) that we are using the `"/autoexecution/search/block/latest"` Overledger URL to search for the latest block. 48 | 49 | See that the response has two main objects due to Overledger’s preparation and execution model: 50 | 51 | 1. *PreparationBlockSearchResponse*: This includes the request id and any QNT fee that must be paid for use of this endpoint. 52 | 2. *ExecutionBlockSearchResponse*: This includes information on the requested block and related metadata. 53 | 54 | The block information will be returned in cross-DLT standardised form and in the DLT specific form (referred to as nativeData). This allows maximum flexibility to software developers, as they can use different DLTs with the same data model. 55 | 56 | ##### Auto Execute Block Search API Response Main Components 57 | 58 | The block response will contain a few main components: 59 | 60 | i. Location: Each Overledger DLT data response includes a reference to the location (technology, network) of where this data was taken from. This helps with auditing. 61 | ii. Status: Overledger responses regarding blocks and transactions come with a status. Due to some DLTs having probabilistic finality of transactions/blocks and other DLTs having deterministic finality of transaction/blocks, the status object is used to indicate to the developer when the requested data is assumed to be final (therefore status.value = “SUCCESSFUL”) or not (therefore status.value=“PENDING”). 62 | iii. Block = The requested block data in standardised and nativeData formats. 63 | 64 | For parameter by parameter descriptions see the [openAPI3 doc](https://docs.overledger.io/#operation/autoExecuteSearchBlockRequest). 65 | 66 | 67 | ##### Auto Execute Block Search API Response Array Values 68 | 69 | Notice that there are two standardised array parameters that have multiple type options, which we will describe here: 70 | 71 | 1. executionBlockSearchResponse.block.hashes - This is an array of all the hashes that describe this block. The options are: 72 | 73 | A. BLOCK_HASH = The hash of this block. Also referred as to as the blockId 74 | 75 | B. PARENT_HASH = The hash of the previous block in the blockchain 76 | 77 | C. CHILD_HASH = the hash of the next block in the blockchain 78 | 79 | D. TRANSACTIONS_HASH = The hash of all the transactions in the block 80 | 81 | E. TRANSACTIONS_MERKLE_ROOT = The Merkle root hash when the transaction of the block are organised into a merkle tree structure 82 | 83 | F. STATE_HASH = The hash of the entire current state of the ledger 84 | 85 | G. STATE_MERKLE_ROOT = The Merkle root hash, when the current ledger state is organised into a Merkle tree structure 86 | 87 | H. TRANSACTIONS_RECEIPT_ROOT = The Merkle root hash, when the transaction processing details are organised into a Merkle tree structure 88 | 89 | 2. executionBlockSearchResponse.block.size - This is an array of all the sizes that describe this block. The options are: 90 | 91 | A. MEMORY = The memory size of the entire block in terms of the total number of bytes. 92 | 93 | B. COMPUTATION = The computational processing size of the block expressed as the number of native individual computational units (e.g. gas for Ethereum, exUnits for Cardano, etc). 94 | 95 | 96 | #### Searching for a Specific Block in Other DLT Networks 97 | 98 | You can search for a specific block via its blockId in the Ethereum Goerli DLT network by running the following: 99 | 100 | ``` 101 | node examples/block-search/autoexecute-specific-blockid-search.js password=MY_PASSWORD 102 | ``` 103 | 104 | You will see in the example script (referenced above) that we are using the `"/autoexecution/search/block/${blockId}"` Overledger URL to search for a block with the given blockId. 105 | 106 | To search for a specific valid blockId, the script firstly searches for the current block using the latest keyword, then finds the blockId of the latest block's parent and uses that blockId to search. 107 | 108 | Because the logic of this file is built on the standardised data model, all we have to do to make the same script applicable to the Bitcoin testnet or the XRP Ledger testnet is to change the location object (in line 63). So give it a go! 109 | 110 | Finally note that you can search for a specific block via the blockId or the block number. You can search for a specific block number in the XRP Ledger DLT network by running the following: 111 | 112 | ``` 113 | node examples/block-search/autoexecute-specific-blocknumber-search.js password=MY_PASSWORD 114 | ``` 115 | 116 | You will see in the example script (referenced above) that we are using the `"/autoexecution/search/block/${blockNumber}"` Overledger URL to search for a block with the given blockNumber. 117 | 118 | The logic of this script is similar to the previous one. I.e. the script searches for the latest block first to find the current block number, and then finds the blockNumber of the latest block's parent and uses that blockNumber to search. Again the Overledger standardised data model is used so again this script can be modified to operate over other DLT Networks by simply changing the location object. 119 | 120 | #### Challenges 121 | 122 | ##### Searching for the Latest Block in Other DLT Networks 123 | 124 | Given the example `examples/block-search/autoexecute-latest-block-search.js` file and the location information listed above, can you understand how to change this file to instead query the latest block on the Ethereum Goerli and XRP Ledger testnets? Hint: Look at the overledgerRequestMetaData object. 125 | 126 | ##### Searching for a Specific Block 127 | 128 | Take a look at a third party explorer for the DLT testnets we are using, e.g. [the Bitcoin Testnet](https://blockstream.info/testnet/), [the Ethereum Goerli Testnet](https://goerli.etherscan.io/), or [the XRP Ledger Testnet](https://blockexplorer.one/xrp/testnet). 129 | 130 | Choose a block from these explorers. Can you understand how to modify the example scripts to search for your chosen block? 131 | 132 | #### Troubleshooting 133 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 134 | 135 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 136 | 137 | #### Error: Bad Decrypt 138 | 139 | Description: 140 | 141 | ``` 142 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 143 | ``` 144 | 145 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 146 | 147 | Solution: provide the password with which .env.enc was encrypted when running the script. 148 | 149 | 150 | #### Error: Encrypted Environment File Does Not Exist 151 | 152 | Description: 153 | 154 | ``` 155 | Secure-env : ERROR OCCURED .env.enc does not exist. 156 | ``` 157 | 158 | Cause: You are missing the encrypted environment file in the folder that you are running from. 159 | 160 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 161 | 162 | #### Error: Missing Password 163 | 164 | Description: 165 | 166 | ``` 167 | Error: Please insert a password to decrypt the secure env file. 168 | ``` 169 | 170 | Cause: You did not include the password as a command line option. 171 | 172 | Solution: Include the password as a command line option as stated in your terminal print out. 173 | -------------------------------------------------------------------------------- /examples/transaction-creation/submit-transactions.js: -------------------------------------------------------------------------------- 1 | // NOTE: You need to have a .env.enc file in the root directory where you are running the node command 2 | 3 | const log4js = require("log4js"); 4 | const OverledgerBundle = require("@quantnetwork/overledger-bundle"); 5 | const OverledgerTypes = require("@quantnetwork/overledger-types"); 6 | 7 | const OverledgerSDK = OverledgerBundle.default; 8 | const courseModule = "submit-transaction"; 9 | const { DltNameOptions } = OverledgerTypes; 10 | 11 | const log = log4js.getLogger(courseModule); 12 | 13 | // Initialize log 14 | log4js.configure({ 15 | appenders: { 16 | console: { type: "console" }, 17 | }, 18 | categories: { 19 | default: { appenders: ["console"], level: "debug" }, 20 | }, 21 | }); 22 | 23 | log.info("Loading password passed in via the command line"); 24 | const SENV_PASSWORD = process.argv[2].split("=")[1]; 25 | const BITCOIN_FUNDING_TX = process.argv[3].split("=")[1]; 26 | 27 | // Check for provided password for the secure env 28 | if (!SENV_PASSWORD) { 29 | log.error( 30 | "Please insert a password to decrypt the secure env file. Example: \n node examples/transaction-creation/submit-transaction.js password=MY_PASSWORD bitcoinTx=MY_BITCOIN_FUNDING_TX", 31 | ); 32 | throw new Error( 33 | "Please insert a password to decrypt the secure env file. Example: \n node examples/transaction-creation/submit-transaction.js password=MY_PASSWORD bitcoinTx=MY_BITCOIN_FUNDING_TX", 34 | ); 35 | } 36 | // Check for provided bitcoin funding transaction 37 | if (!BITCOIN_FUNDING_TX) { 38 | log.error( 39 | "Please insert a bitcoin funding transaction for your address. Example: \n node examples/transaction-creation/submit-transaction.js password=MY_PASSWORD bitcoinTx=MY_BITCOIN_FUNDING_TX", 40 | ); 41 | throw new Error( 42 | "Please insert a bitcoin funding transaction for your address. Example: \n node examples/transaction-creation/submit-transaction.js password=MY_PASSWORD bitcoinTx=MY_BITCOIN_FUNDING_TX", 43 | ); 44 | } 45 | 46 | log.info("Executing ", courseModule); 47 | (async () => { 48 | try { 49 | log.info("Initializing the SDK"); 50 | const overledger = new OverledgerSDK({ 51 | dlts: [ 52 | { dlt: DltNameOptions.BITCOIN }, 53 | { dlt: DltNameOptions.ETHEREUM }, 54 | { dlt: DltNameOptions.XRP_LEDGER }, 55 | ], // connects OVL to these 3 technologies 56 | userPoolID: "us-east-1_xfjNg5Nv9", // where your userpool id is located 57 | provider: { network: "https://api.sandbox.overledger.io/v2" }, // URL for the testnet versions of these DLTs 58 | envFilePassword: SENV_PASSWORD, 59 | }); 60 | log.info("Creating random addresses to send transactions to"); 61 | const bitcoinAccount = await overledger.dlts.bitcoin.createAccount(); 62 | const bitcoinDestination = bitcoinAccount.address; 63 | 64 | const ethAccount = await overledger.dlts.ethereum.createAccount(); 65 | const ethereumDestination = ethAccount.address; 66 | 67 | const xrpAccount = await overledger.dlts["xrp-ledger"].createAccount(); 68 | const xrpLedgerDestination = xrpAccount.address; 69 | 70 | log.info("Loading our private keys from the encrypted .env file"); 71 | // this function is client side only (i.e. there is no interaction with the Overledger DLT gateway calling this function) 72 | overledger.dlts[DltNameOptions.BITCOIN].setAccount({ 73 | privateKey: process.env.BITCOIN_PRIVATE_KEY, 74 | }); 75 | overledger.dlts[DltNameOptions.ETHEREUM].setAccount({ 76 | privateKey: process.env.ETHEREUM_PRIVATE_KEY, 77 | }); 78 | overledger.dlts[DltNameOptions.XRP_LEDGER].setAccount({ 79 | privateKey: process.env.XRP_LEDGER_PRIVATE_KEY, 80 | }); 81 | 82 | log.info("Obtaining the Access Token to Interact with Overledger"); 83 | const refreshTokensResponse = 84 | await overledger.getTokensUsingClientIdAndSecret( 85 | process.env.USER_NAME, 86 | process.env.PASSWORD, 87 | process.env.CLIENT_ID, 88 | process.env.CLIENT_SECRET, 89 | ); 90 | 91 | const overledgerInstance = overledger.provider.createRequest( 92 | refreshTokensResponse.accessToken.toString(), 93 | ); 94 | 95 | log.info("Creating Overledger Request Object with the Correct Location"); 96 | const overledgerRequestMetaData = [ 97 | { 98 | location: { 99 | technology: "Bitcoin", 100 | network: "Testnet", 101 | }, 102 | }, 103 | { 104 | location: { 105 | technology: "Ethereum", 106 | network: "Ethereum Goerli Testnet", 107 | }, 108 | }, 109 | { 110 | location: { 111 | technology: "XRP Ledger", 112 | network: "Testnet", 113 | }, 114 | }, 115 | ]; 116 | 117 | log.info( 118 | "Setting the Correct Transaction Origins, Destinations, Amounts and Units", 119 | ); 120 | 121 | // check the bitcoin funding transaction 122 | const overledgerTransactionSearchResponse = await overledgerInstance.post( 123 | `/autoexecution/search/transaction?transactionId=${BITCOIN_FUNDING_TX}`, 124 | overledgerRequestMetaData[0], 125 | ); 126 | // loop over UTXOs in the funding transaction and wait for a match to the users Bitcoin address 127 | let count = 0; 128 | const bitcoinTxDestinations = 129 | overledgerTransactionSearchResponse.data 130 | .executionTransactionSearchResponse.transaction.destination.length; 131 | let destination; 132 | let bitcoinOrigin; 133 | while (count < bitcoinTxDestinations) { 134 | destination = 135 | overledgerTransactionSearchResponse.data 136 | .executionTransactionSearchResponse.transaction.destination[count]; 137 | if (destination.destinationId == process.env.BITCOIN_ADDRESS) { 138 | bitcoinOrigin = `${BITCOIN_FUNDING_TX}:${count.toString()}`; 139 | } 140 | count += 1; 141 | } 142 | 143 | if (!bitcoinOrigin) { 144 | log.error( 145 | "The providing bitcoin funding transaction does not have a transaction output assigned to your Bitcoin address. Please recheck the provided bitcoinTx.", 146 | ); 147 | throw new Error( 148 | "The providing bitcoin funding transaction does not have a transaction output assigned to your Bitcoin address. Please recheck the provided bitcoinTx.", 149 | ); 150 | } 151 | 152 | // Set the origins. Recall that Account based DLT origins are accountIds, 153 | // whereas UTXO based DLT origins are transactionIds:TransactionOutputIndex (hence the search for the bitcoin origin above) 154 | const overledgerOrigins = [ 155 | bitcoinOrigin, 156 | process.env.ETHEREUM_ADDRESS, 157 | process.env.XRP_LEDGER_ADDRESS, 158 | ]; 159 | 160 | const overledgerDestinations = [ 161 | bitcoinDestination, 162 | ethereumDestination, 163 | xrpLedgerDestination, 164 | ]; 165 | 166 | // We will send be sending the safe amounts of each main protocol token (BTC, ETH, XRP) on each DLT network 167 | // (safe as Bitcoin can require the amount sent to be over a certain level of satoshis) 168 | // (safe as XRPL requires the amount sent to a new address to be over a certain limit) 169 | const overledgerAmounts = ["0.00001", "0.0000000000000001", "10"]; 170 | // Note that as we are connected to the testnet DLT networks, these tokens do not have real world value 171 | const overledgerUnits = ["BTC", "ETH", "XRP"]; 172 | 173 | log.info("Entering loop to prepare, sign and send one transaction\n"); 174 | 175 | count = 0; 176 | let prepareTransactionRequest = {}; 177 | const prepareTransactionResponse = {}; 178 | const executeTransactionRequest = []; 179 | const executeTransactionResponse = []; 180 | while (count < 3){ 181 | // format the transaction request 182 | prepareTransactionRequest = { 183 | type: "PAYMENT", 184 | location: { 185 | technology: overledgerRequestMetaData[count].location.technology, 186 | network: overledgerRequestMetaData[count].location.network, 187 | }, 188 | urgency: "normal", 189 | requestDetails: { 190 | overledgerSigningType: "overledger-javascript-library", 191 | message: "OVL Message Example", 192 | origin: [ 193 | { 194 | originId: overledgerOrigins[count], 195 | }, 196 | ], 197 | destination: [ 198 | { 199 | destinationId: overledgerDestinations[count], 200 | payment: { 201 | amount: overledgerAmounts[count], 202 | unit: overledgerUnits[count], 203 | }, 204 | }, 205 | ], 206 | }, 207 | }; 208 | log.info(`Using DLT: ${overledgerRequestMetaData[count].location.technology}\n`); 209 | 210 | log.info(`OVL prepare transaction request:\n\n${JSON.stringify(prepareTransactionRequest)}\n`); 211 | // send the standardised transaction to Overledger to prepare the native data stucture 212 | prepareTransactionResponse[count] = await overledgerInstance.post( 213 | "/preparation/transaction", 214 | prepareTransactionRequest, 215 | ); 216 | 217 | log.info( 218 | `OVL prepare request response:\n\n${JSON.stringify( 219 | prepareTransactionResponse[count].data, 220 | )}\n`, 221 | ); 222 | 223 | // sign the native transaction 224 | log.info(`Signing transaction\n`); 225 | let signedTransactionResponse = await overledger.sign( 226 | overledgerRequestMetaData[count].location.technology.replace(/\s+/g, '-').toLowerCase(), 227 | prepareTransactionResponse[count].data, 228 | ); 229 | executeTransactionRequest[count] = { 230 | requestId: prepareTransactionResponse[count].data.requestId, 231 | signed: signedTransactionResponse.signedTransaction, 232 | }; 233 | log.info(`OVL execute transaction request ${count}:\n\n${JSON.stringify(executeTransactionRequest[count])}\n`); 234 | // submit the signed transaction to Overledger 235 | executeTransactionResponse[count] = await overledgerInstance.post( 236 | "/execution/transaction", 237 | executeTransactionRequest[count], 238 | ); 239 | log.info( 240 | `Printing Out Overledger's Response for a transaction prepared, signed and submitted onto the ${ 241 | overledgerRequestMetaData[count].location.technology 242 | } testnet:\n\n${JSON.stringify(executeTransactionResponse[count].data)}\n\n`, 243 | ); 244 | count++; 245 | } 246 | } catch (e) { 247 | log.error("", e); 248 | } 249 | })(); 250 | -------------------------------------------------------------------------------- /Exercise1.md: -------------------------------------------------------------------------------- 1 | # Week 1 Introduction to Distributed Ledger Technologies 2 | 3 | ## Distributed Ledger Technologies 4 | 5 | ### Exercise - Establish Your DLT Network Connection 6 | 7 | In this exercise, we are going to setup the development environment and introduce the Github repository containing the code for assignments. 8 | 9 | The distributed ledger technologies studied can be accessed via the Overledger DLT Gateway. We will be using the Overledger Javascript v2 SDK to interact with Overledger. Therefore our development environment will use [Node.js](https://nodejs.org/en/). Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node comes with a package manager called [npm](https://www.npmjs.com/). We will use npm to install the necessary dependencies to our project. 10 | 11 | Note that more details on the SDK used in this course can be found [here](https://github.com/quantnetwork/overledger-sdk-javascript-v2). The examples in this course were made specifically to complement the FutureLearn course theory. There are additional more simple examples using this SDK found [here](https://github.com/quantnetwork/overledger-sdk-javascript-v2/tree/develop/examples). 12 | 13 | *Note that the following works for Mac and Linux based Operating Systems. If you are using a Windows machine it is recommended that you run the exercises via Windows Subsystem for Linux (WSL), see for example [this guide](https://docs.microsoft.com/en-us/windows/wsl/install).* 14 | 15 | #### Obtaining the project 16 | First, you need to download this repository code onto your local computer. To do so, we advise you to [fork the project](https://docs.github.com/en/get-started/quickstart/fork-a-repo) and clone the code. If you fork the project first, you will be able to push your changes into your own repository, so that you can more easily discuss your code with fellow learners. 17 | 18 | To clone the project after you have forked, open a terminal in your desired folder and run: `git clone https://github.com/YOUR_USERNAME/blockchain-developer-exercises-foundations`, where `YOUR_USERNAME` is your Github username). Alternatively, if you have not forked, you can run `git clone https://github.com/quantnetwork/blockchain-developer-exercises-foundations`. 19 | 20 | Now navigate to the exercises folder by typing: 21 | `cd blockchain-developer-exercises-foundations` 22 | 23 | You're almost go to go! But first, we need to install the dependencies. 24 | 25 | #### Installing Correct Node Version 26 | 27 | To manage the different versions of Node.js, we will use the [node version manager (nvm)](https://github.com/nvm-sh/nvm). To do so, on your terminal run: 28 | 29 | ``` 30 | curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash 31 | ``` 32 | 33 | and then: 34 | 35 | ```` 36 | export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")" 37 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 38 | ```` 39 | 40 | nvm is now installed and ready to use. Now we need to install a specific version of node with nvm. Run: 41 | 42 | ``` 43 | nvm install 16.11.0 44 | ``` 45 | 46 | This installed Node.js version 16.11.0. It is important for everyone to have the same Node version, to avoid inconsistent results when it is running modules. Now we make sure we have the latest npm 47 | 48 | ``` 49 | nvm install-latest-npm 50 | ``` 51 | 52 | We now install a tool allowing us to directly run packages on the terminal: 53 | 54 | ``` 55 | npm install -g npm-run 56 | ``` 57 | 58 | Finally, we need to install the dependencies. Before doing so, examine the file that stores the dependencies, called `package.json`. You will notice that this repository has a set of scripts to help development tasks, a set of administrative-related items, and two sets of dependencies. The first set, `dependencies` state the runtime dependencies, this is, the software packages you will need to use with your code. The second set, `devDependencies` set the dependencies for development that are not used in runtime (for example, the linter). 59 | 60 | To install the dependencies, run: 61 | 62 | ``` 63 | npm i 64 | ``` 65 | 66 | The development environment is set! 67 | 68 | #### Signing Up to Overledger 69 | 70 | 71 | 72 | 73 | In the following exercises we will connect to DLT nodes via the Overledger DLT gateway. Overledger provides access to multiple DLT networks of different DLT types, allowing multi-ledger applications (mDapps) to be built. Additionally Overledger utilises a standardised data model, easing the learning process for software developers. 74 | 75 | To use Overledger, you will need to sign up [here](https://developer.quant.network/). 76 | 77 | Once signed up, there are a few steps you need to perform to select the correct environment and add a wallet: 78 | 79 | - Firstly select the "Sandbox V2.0" environment. 80 | - Then scroll to the "Wallets" tab and click on the "Add Wallet" button. 81 | - Select an "Application" wallet. 82 | - Give your wallet a name. 83 | - Add two different Ethereum addresses in your wallet. The specific addresses only become relevant should you want to access main DLT networks through Overledger (all of these course exercises will only use test networks). For these exercises, you can add any Ethereum addresses. But briefly, should you move to mainnet and use our pay-as-you-go pricing model, the QNT address would be the Ethereum address that is paying QNT for specific API calls, whereas the operator address can call functions on our payment channels that implement the pay-as-you-go pricing model. 84 | 85 | After a wallet has been added, you next need to setup an application, to do so: 86 | 87 | - Scroll to the "Applications" tab. 88 | - Click on the "register mDapp" button. 89 | - Give your application a name and select the wallet you just created. 90 | - Note that a callback URL is *not* needed for these developer exercises, as it is used for more advanced features. 91 | 92 | Now you have setup Overledger server side. Next you need to setup client side. 93 | 94 | #### Setting Your Overledger Connection Details 95 | 96 | To connect and use the Overledger DLT Gateway, you need a number of credentials. Take a look at the file *.env.example* This file defines environment variables that our examples will later use. The first four environment variables we will be using are: 97 | 98 | - USER_NAME: which corresponds to your [Overledger Dev Portal](https://developer.quant.network/login) username (typically an e-mail) 99 | - PASSWORD: is your [Overledger Dev Portal](https://developer.quant.network/login) password 100 | - CLIENT_ID: The unique ID of your mDapp. It is obtained through the applications tab of the [Overledger Dev Portal](https://developer.quant.network/user/applications) 101 | - CLIENT_SECRET: The secret associated to your mDapp. Obtained through the applications tab of the [Overledger Dev Portal](https://developer.quant.network/user/applications) 102 | 103 | Note that in Exercise 7 we will be adding to the parameter list in the *.env* file to include private keys and addresses. 104 | 105 | 106 | #### Securing Your Overledger Connection Details 107 | 108 | Since the environment variables information is very sensitive, we want to securely fill them in a file that is then encrypted. To do so, duplicate the *.env.example* file and rename it to *.env*. After that, fill the variables USER_NAME, PASSWORD, CLIENT_ID, and CLIENT_SECRET. You may ignore the remaining ones, for now. 109 | 110 | These variables inside your .env file should now look something like: 111 | 112 | ``` 113 | USER_NAME=firstName.lastName@domain.com 114 | PASSWORD=mySecretPassword 115 | CLIENT_ID=1yyyj5sssf2kkktudddrs2xxx6 116 | CLIENT_SECRET=1aaawrjr133dv383cr1222atg66662f6ccccl5oeeeeeeu5zzzza 117 | ``` 118 | 119 | Now, we will encrypt the *.env* file. For this, run on your terminal (replace MY_PASSWORD for a password of your choice): 120 | 121 | *Note that MY_PASSWORD is a password for the encryption of the .env file and decryption of the .env.enc file. It does not related to your Overledger password described in the previous section.* 122 | 123 | ``` 124 | npm-run secure-env .env -s MY_PASSWORD 125 | ``` 126 | or 127 | ``` 128 | secure-env .env -s MY_PASSWORD 129 | ``` 130 | 131 | Note that if you are running on windows and the above did not work, try: 132 | ``` 133 | ./node_modules/secure-env/dist/es5/lib/cli.js .env -s MY_PASSWORD 134 | ``` 135 | 136 | Great! Now we have an encrypted env file, *.env.enc*, that will be parsed and securely read by the SDK. For extra security, delete *.env*. 137 | 138 | When looking into our scripts that connects to Overledger, you may notice that connections to Overledger use the OAuth2 protocol, meaning your interactions with Overledger are mediated by an access token. 139 | 140 | To see an example of these tokens, run the following script (make sure to replace MY_PASSWORD by the password you used to encrypt *.env*): 141 | 142 | ``` 143 | node examples/configuration/configure-sdk.js password=MY_PASSWORD 144 | ``` 145 | 146 | Great! You can see how tokens are obtained from Overledger. 147 | 148 | You are provided with an `access token`, which allows our examples to make API requests on behalf of you. In practice, you are issuing requests to Overledger, and this token authenticates you. It is important that you keep this token private, as someone holding it can make requests to Overledger on your behalf. The access token has an expiration date, typically ranging from a few hours to a few weeks. 149 | 150 | The access tokens is a [JSON Web Tokens (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token). "JWTs are an open, industry standard RFC 7519 method for representing claims securely between two parties". 151 | 152 | You can inspect the content of token with a [JWT decoder](https://jwt.io/). 153 | 154 | 155 | ### Troubleshooting 156 | This exercise was tested firstly in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 157 | 158 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 159 | 160 | #### Error: bad decrypt 161 | 162 | Description: 163 | 164 | ``` 165 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 166 | ``` 167 | 168 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 169 | 170 | Solution: provide the password with which .env.enc was encrypted when running the script. 171 | 172 | #### Error: ENOENT 173 | 174 | Description: 175 | ``` 176 | Error: spawn secure-env ENOENT 177 | ``` 178 | Cause: If you are running on windows you can encounter this error. 179 | 180 | Solution: Run `./node_modules/secure-env/dist/es5/lib/cli.js .env -s MY_PASSWORD`. If you are still having trouble, consult [here](https://www.npmjs.com/package/secure-env#encrypt-env) for alternative command line options. 181 | 182 | #### Error: Both UserPoolId and ClientId are required 183 | 184 | Description: 185 | ``` 186 | Error: Both UserPoolId and ClientId are required. 187 | ``` 188 | 189 | Cause: This can be caused by a few possible client side issues. 190 | 191 | Solution: Firstly make sure that your userPoolId has not been edited from the default value. Secondly, check that you have all the requested parameters in your .env file as described [here](https://github.com/quantnetwork/blockchain-developer-exercises-foundations/blob/main/Exercise1.md#setting-your-overledger-connection-details) AND that you have deleted the current .env.enc file and re-encypted your .env file via the process described [here](https://github.com/quantnetwork/blockchain-developer-exercises-foundations/blob/main/Exercise1.md#securing-your-overledger-connection-details). If there are still issues remaining consider and resolve any possible typos introduced into the .env file for the parameter names, especially the CLIENT_ID one. 192 | -------------------------------------------------------------------------------- /Exercise7.md: -------------------------------------------------------------------------------- 1 | # Week 3 Cryptography in Distributed Ledger Technologies 2 | 3 | ## Exercise - Create your first DLT transactions 4 | 5 | In this exercise, we will create our own transactions via Overledger’s prepareTransactionRequest and executePreparedRequestTransaction APIs. The documentation for these endpoints can be found [here](https://docs.overledger.io/#operation/prepareTransactionRequest) and [here](https://docs.overledger.io/#operation/executePreparedRequestTransaction) respectively. 6 | 7 | 8 | ### DLT Network Information 9 | 10 | We will be interacting with the Bitcoin, Ethereum & XRP Ledger testnets. The relevant Overledger location objects are as follows: 11 | 12 | 1. `Location = {“technology”: “Bitcoin”, “network”: “Testnet”}` 13 | 14 | 2. `Location = {“technology”: “Ethereum”, “network”: “Ethereum Goerli Testnet”}` 15 | 16 | 3. `Location = {“technology”: “XRP Ledger”, “network”: “Testnet”}` 17 | 18 | ### Prerequisites 19 | 20 | It is assumed that you have already setup your environment by following [these instructions](./Exercise1.md) and that you have completed the previous exercises that use Overledger to read data from the DLT networks. 21 | 22 | ### Creating Accounts 23 | 24 | You will need to have DLT accounts to create transactions. The following example will create and console log new DLT accounts for all of the Bitcoin testnet, Ethereum Goerli testnet and the XRP ledger testnet: 25 | 26 | ``` 27 | node examples/account-creation/generate-accounts.js 28 | ``` 29 | 30 | **NOTE that key pairs can be reused on different DLT networks including mainnets, so we recommend for you to only use these generated accounts for these tutorials.** 31 | 32 | **TAKEAWAY: Do not mix the accounts that you use on testnets and mainnets!** 33 | 34 | ### Attaining Testnet Cryptocurrency 35 | 36 | As we are interacting with permissionless DLT networks, we will have to pay a transaction fee to get our transactions accepted on the DLT networks. As we are also interacting with testnets, the transaction fee will actually be paid in a test cryptocurrency (typically without monetary value). But the transaction fee system is still present on the testnets, in order to accurately simulate the mainnets. 37 | 38 | Therefore you will need to fund your addresses before you can send transactions from those addresses. To fund your addresses on testnets, we can request a faucet to provide us some funds. These services are named as faucets as they "drip" funds to users that require them. 39 | 40 | We list a few publicly available faucets options below: 41 | - Ethereum Goerli Testnet: https://goerli-faucet.pk910.de/ or https://goerli-faucet.mudit.blog/ 42 | - XRP Ledger Testnet: https://xrpl.org/xrp-testnet-faucet.html 43 | - Bitcoin Testnet: https://bitcoinfaucet.uo1.net/ or https://coinfaucet.eu/en/btc-testnet/ or https://testnet-faucet.mempool.co/ 44 | 45 | Note that faucets can be occasionally empty or can change frequently, if your having trouble with all of the ones listed above for your desired DLT, have a Google and let us know if you find a better one. 46 | 47 | **IMPORTANT NOTE 1: For Bitcoin and Ethereum, note that the faucet funding transactions needs to be in a block of the corresponding blockchain before it can be used. Whereas XRP ledger funds will be available as soon as you see the generated information on the XRP ledger faucet screen.** 48 | 49 | **IMPORTANT NOTE 2: For Bitcoin you will need to note down the transactionId the faucet uses to provide you with funds. This is so the example below will run correctly. You should also request at least 0.0001 BTC from any faucet you use.** 50 | 51 | **IMPORTANT NOTE 3: For Ethereum, there are many different test networks available (e.g. Goerli, Rinkeby, Kovan,...). Tokens issued on one test network cannot be used on another test network. So make sure that any faucet you use is a Goerli testnet faucet.** 52 | 53 | **IMPORTANT NOTE 4: For XRP Ledger testnet, we recommend using the DLT account generated by the faucet (instead of the XRP Ledger account generated by the script above) as it automatically comes provided with testnet XRP.** 54 | 55 | 56 | ### Adding DLT accounts to the Environment File 57 | 58 | To use these generated accounts (or any others), recall the *.env.example.* from Exercise 1. This file defines environment variables. In Exercise 1, we set the USER_NAME, PASSWORD, CLIENT_ID and CLIENT_SECRET environment variables. In this class we still require those previous four variables but now we will also enter the following: 59 | 60 | - BITCOIN_PRIVATE_KEY: set this equal to the BitcoinAccount.privateKey parameter provided by the generate-accounts.js script 61 | - ETHEREUM_PRIVATE_KEY: set this equal to the EthereumAccount.privateKey parameter provided by the generate-accounts.js script 62 | - XRP_LEDGER_PRIVATE_KEY: set this equal to the [XRP ledger faucet](https://xrpl.org/xrp-testnet-faucet.html) generated secret for testnet 63 | - BITCOIN_ADDRESS: set this equal to the BitcoinAccount.address provided by the generate-accounts.js script 64 | - ETHEREUM_ADDRESS: set this equal to the EthereumAccount.address provided by the generate-accounts.js script 65 | - XRP_LEDGER_ADDRESS: set this equal to the [XRP ledger faucet](https://xrpl.org/xrp-testnet-faucet.html) generated address for testnet 66 | 67 | Therefore you will once again need to setup the *.env.enc* file as stated in Exercise 1. In particular, you need to duplicate the *.env.example* file and rename it to *.env*. Make sure to set the previous four parameters from Exercise 1 and the new six parameters from this class in *.env*. You will also need to once again encrypt the *.env* file. For this, run on your terminal (replace MY_PASSWORD for a password of your choice): 68 | 69 | ``` 70 | npm-run secure-env .env -s MY_PASSWORD 71 | ``` 72 | 73 | ### Creating Transactions 74 | 75 | This example will create transactions on the Bitcoin testnet, Ethereum Goerli testnet and the XRP ledger test: 76 | 77 | **REMINDER: For Bitcoin and Ethereum, the faucet funding transactions needs to be in a block of the corresponding blockchain before it can be used. Otherwise the following script will output an error.** 78 | 79 | ``` 80 | node examples/transaction-creation/submit-transactions.js password=MY_PASSWORD fundingTx=MY_BITCOIN_FUNDING_TX 81 | ``` 82 | 83 | Note that an extra command line parameter is required, which is a bitcoin transaction that you have received from the bitcoin faucet and includes an unspent transaction output that you have *not yet used*. 84 | 85 | You will see in the example script that we are using: 86 | 87 | ``` 88 | await overledgerInstance.post( 89 | "/preparation/transaction", 90 | prepareTransactionRequest, 91 | ); 92 | ``` 93 | to request that an Overledger standardised transaction is transformed into a transaction in the native DLT format ready for signing. We are subsequently using: 94 | 95 | ``` 96 | await overledger.sign( 97 | overledgerRequestMetaData[count].location.technology.replace(/\s+/g, '-').toLowerCase(), 98 | prepareTransactionResponse[count].data, 99 | ); 100 | ``` 101 | to sign the transaction and: 102 | 103 | ``` 104 | await overledgerInstance.post( 105 | "/execution/transaction", 106 | executeTransactionRequest, 107 | ); 108 | ``` 109 | to send the transaction to Overledger, who forwards it onto the DLT network. 110 | 111 | The full details of this script is as follows. It firstly creates random DLT accounts to send the transactions to. Next the script makes sure that you provided a valid bitcoin funding transaction by fetching the given transaction and looping over it's UTXOs looking for a match to your Bitcoin address. If it doesn't find it, it means that either the transaction is not yet in a block of the blockchain, or the provided transaction is not the funding transaction issued by the faucet, or you have provided the wrong Bitcoin address to *.env*, or the faucet is not working correctly. 112 | 113 | After this, the script constructs the origin and destination of each of your bitcoin, ethereum and XRP ledger transactions. Recall that an accounts based transaction origin is an address, whereas a UTXO based transaction origin is a UTXOid (transactionId:OutputIndex). Whereas the transaction destination of both accounts and UTXO transactions is an address. The script then sets the value of BTC, ETH or XRP to be transferred via these transactions. Note that as transaction fees in BTC are implicit, the transaction fee is taken from the last destination. 114 | 115 | Now the script has enough information to submit each transaction to the DLT network. Therefore the script enters a loop which completes three main steps for each DLT network. Firstly the script sends a standardised transaction to OVL to be prepared and receives the response in the DLT native data format. Secondly the script signs the native data form of the transaction. Thirdly the script sends the signed transaction to Overledger for it to be added onto the DLT network. 116 | 117 | 118 | #### Overledger Execute Transaction API Response 119 | 120 | The execute transaction response will contain a few main components: 121 | 122 | 1. Location: Each Overledger DLT data response includes a reference to the location (technology, network) of where this data was taken from. This helps with auditing. 123 | 124 | 2. Status: Overledger responses regarding blocks and transactions come with a status. Due to some DLTs having probabilistic finality of transactions/blocks and other DLTs having deterministic finality of transaction/blocks, the status object is used to indicate to the developer when the requested data is assumed to be final (therefore status.value = “SUCCESSFUL”) or not (therefore status.value=“PENDING”). 125 | 126 | 3. TransactionId: The id of the submitted signed transaction in the DLT native data format. This is the transaction id required for the OVL transaction Search APIs. 127 | 128 | 4. OverledgerTransactionId: An overledger specific transaction id used for tracking a transaction status and for linking transactions to a particular client created decentralised application. 129 | 130 | For parameter by parameter descriptions see the [documentation](https://docs.overledger.io/#operation/executePreparedRequestTransaction). 131 | 132 | 133 | #### Challenges 134 | 135 | ##### Sending Transactions to Specific Addresses 136 | 137 | Given the example `./examples/transaction-creation/submit-transactions.js` file, can you understand how to change this file to send transactions to specific addresses that you choose from each DLT network? How will you choose these addresses? 138 | 139 | ##### Sending Transactions for a Specific Amount 140 | 141 | Given the example `examples/transaction-creation/submit-transactions.js` file, can you understand how to change this file to send specific amounts of your choosing? Do you have any limitations on the amounts that you choose and how can you modify the code to deal with this issue? 142 | 143 | #### Troubleshooting 144 | This exercise was tested in Ubuntu 20.04.2 LTS Release: 20.04 Codename: focal, with nvm version 0.35.3, and node version 16.3.0. 145 | 146 | This exercise was additionally tested in MacOS Monterey Version 12.0.1, with nvm version 0.39.0, and node version 16.3.0. 147 | 148 | #### Error: bad decrypt 149 | 150 | Description: 151 | 152 | ``` 153 | Secure-env : ERROR OCCURED Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt 154 | ``` 155 | 156 | Cause: the secure env package cannot decrypt the .env.enc file because the provided password was incorrect. 157 | 158 | Solution: provide the password with which .env.enc was encrypted when running the script. 159 | 160 | #### Error: .env.enc does not exist 161 | 162 | Description: 163 | 164 | ``` 165 | Secure-env : ERROR OCCURED .env.enc does not exist. 166 | ``` 167 | 168 | Cause: You are missing the encrypted environment file in the folder that you are running from. 169 | 170 | Solution: Return to the top level folder and encrypt .env as described in Exercise 1. 171 | 172 | #### Error: Missing Password 173 | 174 | Description: 175 | 176 | ``` 177 | Error: Please insert a password to decrypt the secure env file. 178 | ``` 179 | 180 | Cause: You did not include the password as a command line option. 181 | 182 | Solution: Include the password as a command line option as stated in your terminal print out. 183 | 184 | #### Error: bitcoin transaction failure 185 | 186 | Status returned from Overledger regarding a bitcoin transaction execution: 187 | 188 | ``` 189 | {"value":"FAILED","code":"TXN1501","description":"-25: Missing inputs","message":"Missing inputs","timestamp":"2022-01-24T22:42:10.282652Z"} 190 | ``` 191 | 192 | Cause: You have either: (1) provided the wrong Bitcoin address to *.env*; or (2) the comand line provided transaction is not the funding transaction issued by the faucet; or (3) the faucet funding transaction has not yet be included in a block of the blockchain; or (4) you have already spent the output of this transaction; or (5) the faucet is not working correctly. 193 | 194 | Solution: Firstly check that the provided fundingTx in the command line variables is correct. This is the most likely error. Check that this transaction has been included in a block of the blockchain and that the destination (output) related to your address is not yet spent. If these are all correct, check the bitcoin address and related private key is correct in the *.env* file, if it is not correct then you need to re-enter the bitcoin address and private key, re-encrypt the *.env* file and you need to request again funds from the bitcoin faucet to send the funds to the new address you added to the *.env* file. Finally if all these checks pass, assume the bitcoin faucet you choose is faulty and find another one. 195 | 196 | #### Error: incorrect bitcoin transaction provided 197 | 198 | Description: 199 | 200 | ``` 201 | Error: The providing bitcoin funding transaction does not have a transaction output assigned to your Bitcoin address. Please recheck the provided bitcoinTx. 202 | ``` 203 | 204 | Cause: The Bitcoin transaction id you provided in the command line does not have a destination (output) that is assigned to your address. 205 | 206 | Solution: Recheck the transaction that the faucet send to your address and correct the terminal entry. 207 | 208 | 209 | #### Error: failed to map overledger standardised transaction to bitcoin native data. 210 | 211 | Overledger rejects the bitcoin preparation request. 212 | 213 | Description: 214 | ``` 215 | Error: Request failed with status code 500 216 | ``` 217 | 218 | with the following in the data object: 219 | 220 | ``` 221 | description: 'Failed to map the Overledger Transaction request to the native standard.' 222 | ``` 223 | 224 | Cause: The is an issue with the formatting of your preparation request. Most likely your BTC payment amount is too low (meaning that the recommend transaction fee to add the transaction to the block would lead to a negative payment value). 225 | 226 | Solution: Raise you BTC payment amount (but it is still required to be less than the given amount from the faucet) 227 | 228 | 229 | #### Error: Insufficient funds for ethereum transaction 230 | 231 | Status returned from Overledger regarding a ethereum transaction execution: 232 | 233 | ``` 234 | {"value":"FAILED","code":"TXN1501","description":"-32000: insufficient funds for gas * price + value","message":"insufficient funds for gas * price + value","timestamp":"1645196615"}} 235 | ``` 236 | 237 | Cause: You have either: (1) provided the wrong Ethereum address to *.env*; or (2) the faucet funding transaction has not yet be included in a block of the blockchain; or (3) you have already spent all of your faucet provided funds. 238 | 239 | Solution: Firstly check that your ethereum address has a balance. This is the most likely error. If it doesn't, check that that the funding transaction has been included in a block of the blockchain. If these are all fine, check the ethereum address and related private key is correct in the *.env* file, if it is not correct then you need to re-enter the ethereum address and private key, re-encrypt the *.env* file and you may need to request again funds from the ethereum faucet to the new address you added to the *.env* file. Finally if all these checks pass, assume the ethereum faucet you choose is faulty and find another one. 240 | 241 | 242 | #### Error: failed to map overledger standardised transaction to xrp ledger native data 243 | 244 | Overledger rejects the xrp ledger preparation request. 245 | 246 | Description: 247 | ``` 248 | Error: Request failed with status code 500 249 | ``` 250 | 251 | with the following in the data object: 252 | 253 | ``` 254 | description: 'Failed to map the Overledger Transaction request to the native standard.' 255 | ``` 256 | 257 | Cause: The is an issue with the formatting of your preparation request. Most likely your XRP payment amount is too high (as your XRPL address is not funded). 258 | 259 | Solution: Use the XRP address and secret generated from the [xrp ledger testnet faucet](https://xrpl.org/xrp-testnet-faucet.html). --------------------------------------------------------------------------------