diff --git a/content/_code-samples/airgapped-wallet/js/.gitignore b/content/_code-samples/airgapped-wallet/js/.gitignore
new file mode 100644
index 0000000000..5e5994e3b0
--- /dev/null
+++ b/content/_code-samples/airgapped-wallet/js/.gitignore
@@ -0,0 +1 @@
+Wallet/
\ No newline at end of file
diff --git a/content/_code-samples/airgapped-wallet/js/README.md b/content/_code-samples/airgapped-wallet/js/README.md
new file mode 100644
index 0000000000..080eccd85d
--- /dev/null
+++ b/content/_code-samples/airgapped-wallet/js/README.md
@@ -0,0 +1,92 @@
+# Airgapped Wallet
+Airgapped describes a state where a device or a system becomes fully disconnected from other devices and systems. It is the maximum protection for a system against unwanted visitors/viruses, this allows any sensitive data like a private key to be stored without worry of it being compromised as long as reasonable security practices are being practiced.
+
+This airgapped XRP wallet allows users to sign a Payment transaction in a secure environment without the private key being exposed to a machine connected to the internet. The private key and seed is encrypted by password and stored securely.
+
+*Note*: You should not use this airgapped wallet in production, it should only be used for educational purposes only.
+
+This code sample consists of 2 parts:
+
+- `airgapped-wallet.js` - This code should be stored in a standalone airgapped machine, it consist of features to generate a wallet, store a keypair securely, sign a transaction and share the signed transaction via QR code.
+- `relay-transaction.js` - This code could be stored in any online machine, no credentials is stored on this code other than a signed transaction which would be sent to an XRPL node for it to be validated on the ledger.
+
+Preferably, `airgapped-wallet.js` should be on a Linux machine while `relay-transaction.js` could be on any operating system.
+
+# Security Practices
+Strongly note that an airgapped system's security is not determined by its code alone but the security practices that are being followed by an operator.
+
+There are channels that can be maliciously used by outside parties to infiltrate an airgapped system and steal sensitive information.
+
+There are other ways malware could interact across airgapped networks, but they all involve an infected USB drive or a similar device introducing malware onto the airgapped machine. They could also involve a person physically accessing the computer, compromising it and installing malware or modifying its hardware.
+
+This is why it is also recommended to encrypt sensitive information being stored in an airgapped machine.
+
+The airgapped machine should have a few rules enforced to close any possible channels getting abused to leak information outside of the machine:
+
+### Wifi
+
+- Disable any wireless networking hardware on the airgapped machine. For example, if you have a desktop PC with a Wifi card, open the PC and remove the Wifi hardware. If you cannot do that, you could go to the system’s BIOS or UEFI firmware and disable the Wifi hardware.
+
+### BlueTooth
+
+- BlueTooth can be maliciously used by neighboring devices to steal data from an airgapped machine. It is recommended to remove or disable the BlueTooth hardware.
+
+### USB
+
+- The USB port can be used to transfer files in and out of the airgapped machine and this may act as a threat to an airgapped machine if the USB drive is infected with a malware. So after installing & setting up this airgapped wallet, it is highly recommended to block off all USB ports by using a USB blocker and not use them.
+
+Do not reconnect the airgapped machine to a network, even when you need to transfer files! An effective airgapped machine should only serve 1 purpose, which is to store data and never open up a gateway for hackers to abuse and steal data.
+
+# Tutorial
+For testing purposes, you would need to have 2 machines and 1 phone in hand to scan the QR code.
+
+1. 1st machine would be airgapped, following the security practices written [here](#security-practices). It stores and manages an XRPL Wallet.
+2. 2nd machine would be a normal computer connected to the internet. It relays a signed transaction blob to a rippled node.
+3. The phone would be used to scan a QR code, which contains a signed transaction blob. The phone would transmit it to the 2nd machine.
+
+The diagram below shows you the process of submitting a transaction to the XRPL:
+
+
+
+
+# Setup
+- Machine 1 - An airgapped computer (during setup, it must be connected to the internet to download the files)
+- Machine 2 - A normal computer connected to the internet
+- Phone - A normal phone with a working camera to scan a QR
+
+## Machine 1 Setup
+Since this machine will be airgapped, it is best to use Linux as the Operating System.
+
+1. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/airgapped-wallet/js) directory
+
+2. Import all the modules required by running: `npm install`
+
+3. Airgap the machine by following the security practices written [here](#security-practices).
+
+4. Run `node airgapped-wallet.js`
+
+5. Scan the QR code and fund the account using the [testnet faucet](https://test.bithomp.com/faucet/)
+
+6. Re-run the script and input '1' to generate a new transaction by following the instructions.
+
+7. Use your phone to scan the QR code, then to send the signed transaction to Machine 2 for submission
+
+## Phone Setup
+The phone requires a working camera that is able to scan a QR code and an internet connection for it to be able to transmit the signed transaction blob to Machine 2.
+
+Once you have signed a transaction in the airgapped machine, a QR code will be generated which will contain the signed transaction blob. Example:
+
+
+
+Scan the QR code using the phone, copy it to the clipboard, and transmit it to Machine 2, which will then be sending it to a rippled node.
+
+You can send a message to yourself using Discord, WhatsApp or even e-mail, then open up the message using Machine 2 to receive the signed transaction blob.
+
+## Machine 2 Setup
+This machine will be used to transmit a signed transaction blob from Machine 1, it would require internet access.
+
+1. Clone all the files under the [`airgapped-wallet`](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/airgapped-wallet/js) directory
+
+2. Import all the modules required by running `npm install`
+
+3. Run `relay-transaction.js` and copy-and-paste the received output of Machine 1 when prompted
diff --git a/content/_code-samples/airgapped-wallet/js/airgapped-wallet.js b/content/_code-samples/airgapped-wallet/js/airgapped-wallet.js
new file mode 100644
index 0000000000..22f8b623c5
--- /dev/null
+++ b/content/_code-samples/airgapped-wallet/js/airgapped-wallet.js
@@ -0,0 +1,223 @@
+const crypto = require("crypto")
+const fs = require('fs')
+const fernet = require("fernet");
+const open = require('open');
+const path = require('path')
+const prompt = require('prompt')
+const { generateSeed, deriveAddress, deriveKeypair } = require("ripple-keypairs/dist/")
+const QRCode = require('qrcode')
+const xrpl = require('xrpl')
+
+const demoAccountSeed = 'sskwYQmxT7SA37ceRaGXA5PhQYrDS'
+const demoAccountAddress = 'rEDd3Wy76Ta1WqfDP2DcnBKHu31SpSiUQrS'
+
+const demoDestinationSeed = 'sEdVokfq7fVXXjZTii2WhtpqGbJni6s'
+const demoDestinationAddress = 'rBgNowfkmPczhMjHRYnBPsuSodDHWHQLdj'
+
+const FEE = '12'
+const LEDGER_OFFSET = 300
+const WALLET_DIR = 'Wallet'
+
+/**
+ * Generates a new (unfunded) wallet
+ *
+ * @returns {{address: *, seed: *}}
+ */
+createWallet = function () {
+ const seed = generateSeed()
+ const {publicKey, privateKey} = deriveKeypair(seed)
+ const address = deriveAddress(publicKey)
+
+ console.log(
+ "XRP Wallet Credentials " +
+ "Wallet Address: " + address +
+ "Seed: " + seed
+ )
+
+ return {address, seed}
+}
+
+/**
+ * Signs transaction and returns signed transaction blob in QR code
+ *
+ * @param xrpAmount
+ * @param destination
+ * @param ledgerSequence
+ * @param walletSequence
+ * @param password
+ * @returns {Promise}
+ */
+signTransaction = async function (xrpAmount, destination, ledgerSequence, walletSequence, password) {
+
+ const salt = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt')).toString()
+
+ const encodedSeed = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt')).toString()
+
+ // Hashing salted password using Password-Based Key Derivation Function 2
+ const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
+
+ // Generate a Fernet secret we can use for symmetric encryption
+ const secret = new fernet.Secret(derivedKey.toString('base64'));
+
+ // Generate decryption token
+ const token = new fernet.Token({
+ secret: secret,
+ token: encodedSeed,
+ ttl: 0
+ })
+ const seed = token.decode();
+
+ const wallet = xrpl.Wallet.fromSeed(seed)
+
+ const paymentTx = {
+ 'TransactionType': 'Payment',
+ 'Account': wallet.classicAddress,
+ 'Amount': xrpl.xrpToDrops(xrpAmount),
+ 'Destination': destination
+ }
+
+ // Normally we would fetch certain needed values like Fee,
+ // LastLedgerSequence snd programmatically, like so:
+ //
+ // const preparedTx = await client.autofill(paymentTx)
+ //
+ // But since this is an airgapped wallet without internet
+ // connection, we have to do it manually:
+ //
+ // paymentTx.Sequence is set in setNextValidSequenceNumber() via sugar/autofill
+ // paymentTx.LastLedgerSequence is set in setLatestValidatedLedgerSequence() via sugar/autofill
+ // paymentTx.Fee is set in getFeeXrp() via sugar/getFeeXrp
+
+ paymentTx.Sequence = walletSequence
+ paymentTx.LastLedgerSequence = ledgerSequence + LEDGER_OFFSET
+ paymentTx.Fee = FEE
+
+ const signedTx = wallet.sign(paymentTx)
+
+ fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'tx_blob.txt'), signedTx.tx_blob)
+ QRCode.toFile(path.join(__dirname, WALLET_DIR , 'tx_blob.png'), signedTx.tx_blob)
+
+ open(path.join(__dirname, WALLET_DIR , 'tx_blob.png'))
+}
+
+main = async function () {
+
+ if (!fs.existsSync(WALLET_DIR )) {
+ // Create Wallet directory in case it does not exist yet
+ fs.mkdirSync(path.join(__dirname, WALLET_DIR ));
+ }
+
+ if (!fs.existsSync(path.join(__dirname, WALLET_DIR , 'address.txt'))) {
+ // Generate a new (unfunded) Wallet
+ const {address, seed} = createWallet()
+
+ prompt.start();
+
+ const {password} = await prompt.get([{
+ name: 'password',
+ description: 'Creating a brand new Wallet, please enter a new password \n Enter Password:',
+ type: 'string',
+ required: true
+ }])
+
+ prompt.stop();
+
+ const salt = crypto.randomBytes(20).toString('hex')
+
+ fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'salt.txt'), salt);
+
+ // Hashing salted password using Password-Based Key Derivation Function 2
+ const derivedKey = crypto.pbkdf2Sync(password, salt, 1000, 32, 'sha256')
+
+ // Generate a Fernet secret we can use for symmetric encryption
+ const secret = new fernet.Secret(derivedKey.toString('base64'));
+
+ // Generate encryption token with secret, time and initialization vector
+ // In a real-world use case we would have current time and a random IV,
+ // but for demo purposes being deterministic is just fine
+ const token = new fernet.Token({
+ secret: secret,
+ time: Date.parse(1),
+ iv: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+ })
+
+ const privateKey = token.encode(seed)
+
+ fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'seed.txt'), privateKey)
+ fs.writeFileSync(path.join(__dirname, WALLET_DIR , 'address.txt'), address)
+ QRCode.toFile(path.join(__dirname, WALLET_DIR , 'address.png'), address)
+
+ console.log(''
+ + 'Finished generating an account.\n'
+ + 'Wallet Address: ' + address + '\n'
+ + 'Please scan the QR code on your phone and use https://test.bithomp.com/faucet/ to fund the account.\n'
+ + 'After that, you\'re able to sign transactions and transmit them to Machine 2 (online machine).')
+
+ return
+ }
+
+ prompt.start();
+
+ console.log(''
+ + '1. Transact XRP.\n'
+ + '2. Generate an XRP wallet (read only)\n'
+ + '3. Showcase XRP Wallet Address (QR Code)\n'
+ + '4. Exit')
+
+ const {menu} = await prompt.get([{
+ name: 'menu',
+ description: 'Enter Index:',
+ type: 'integer',
+ required: true
+ }])
+
+ if (menu === 1) {
+ const {
+ password,
+ xrpAmount,
+ destinationAddress,
+ accountSequence,
+ ledgerSequence
+ } = await prompt.get([{
+ name: 'password',
+ description: 'Enter Password',
+ type: 'string',
+ required: true
+ }, {
+ name: 'xrpAmount',
+ description: 'Enter XRP To Send',
+ type: 'number',
+ required: true
+ }, {
+ name: 'destinationAddress',
+ description: 'If you just want to try it out, you can use the faucet account rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe. Enter Destination',
+ type: 'string',
+ required: true
+ }, {
+ name: 'accountSequence',
+ description: 'Look up the \'Next Sequence\' for the account using test.bithomp.com and enter it',
+ type: 'integer',
+ required: true
+ }, {
+ name: 'ledgerSequence',
+ description: 'Look up the latest ledger sequence on testnet.xrpl.org and enter it below!',
+ type: 'integer',
+ required: true
+ }])
+
+ await signTransaction(xrpAmount, destinationAddress, ledgerSequence, accountSequence, password)
+ } else if (menu === 2) {
+ const {address, seed} = createWallet()
+ console.log('Generated readonly Wallet (address: ' + address + ' seed: ' + seed + ')')
+ } else if (menu === 3) {
+ const address = fs.readFileSync(path.join(__dirname, WALLET_DIR , 'address.txt')).toString()
+ console.log('Wallet Address: ' + address)
+ open(path.join(__dirname, WALLET_DIR , 'address.png'))
+ } else {
+ return
+ }
+
+ prompt.stop();
+}
+
+main()
\ No newline at end of file
diff --git a/content/_code-samples/airgapped-wallet/js/package.json b/content/_code-samples/airgapped-wallet/js/package.json
new file mode 100644
index 0000000000..e05e42ebe0
--- /dev/null
+++ b/content/_code-samples/airgapped-wallet/js/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "airgapped-wallet",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "fernet": "^0.4.0",
+ "open": "^8.4.0",
+ "pbkdf2-hmac": "^1.1.0",
+ "prompt": "^1.3.0",
+ "qrcode": "^1.5.1",
+ "xrpl": "^2.0.0"
+ },
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ }
+}
diff --git a/content/_code-samples/airgapped-wallet/js/relay-transaction.js b/content/_code-samples/airgapped-wallet/js/relay-transaction.js
new file mode 100644
index 0000000000..4f5cfe3f1c
--- /dev/null
+++ b/content/_code-samples/airgapped-wallet/js/relay-transaction.js
@@ -0,0 +1,37 @@
+const prompt = require('prompt')
+const xrpl = require('xrpl')
+
+sendTransaction = async function (tx_blob) {
+ const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233')
+ await client.connect()
+
+ console.log("Connected to node")
+
+ const tx = await client.submitAndWait(tx_blob)
+
+ const txHash = tx.result.hash
+ const txDestination = tx.result.Destination
+ const txXrpAmount = xrpl.dropsToXrp(tx.result.Amount)
+ const txAccount = tx.result.Account
+
+ console.log("XRPL Explorer: https://testnet.xrpl.org/transactions/" + txHash)
+ console.log("Transaction Hash: " + txHash)
+ console.log("Transaction Destination: " + txDestination)
+ console.log("XRP sent: " + txXrpAmount)
+ console.log("Wallet used: " + txAccount)
+
+ await client.disconnect()
+}
+
+main = async function () {
+ const {tx_blob} = await prompt.get([{
+ name: 'tx_blob',
+ description: 'Set tx to \'tx_blob\' received from scanning the QR code generated by the airgapped wallet',
+ type: 'string',
+ required: true
+ }])
+
+ await sendTransaction(tx_blob)
+}
+
+main()
\ No newline at end of file