diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 91e1db0b..296fc734 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -2,20 +2,19 @@ name: Build
on:
push:
- branches: ['main']
+ branches: ['main']
pull_request:
- branches: ['*']
-
+ branches: ['*']
jobs:
- build:
- name: Build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - uses: sonarsource/sonarqube-scan-action@master
- env:
- SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
- SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ - uses: sonarsource/sonarqube-scan-action@master
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index bf0b2a0e..6df9b235 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -51,6 +51,7 @@ jobs:
VITE_HORIZON_NETWORK_PASSPHRASE: Test SDF Network ; September 2015
VITE_STELLAR_NETWORK: ${{ secrets.VITE_STELLAR_NETWORK }}
CYPRESS_SIMPLE_SIGNER_PRIVATE_KEY: ${{ secrets.SIMPLE_SIGNER_PRIVATE_KEY }}
+ VITE_HORIZON_URL: ${{ secrets.VITE_HORIZON_URL }}
test:
runs-on: ubuntu-latest
needs: install-cache
diff --git a/README.md b/README.md
index 6c7c649d..18ab2545 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ you want to contribute.
# How to implement Simple Signer on your website
-Simple Signer provides two endpoints, `/connect` and `/sign` which allow some customisations to be made.
+Simple Signer provides three endpoints, `/connect`, `/sign` and `/payment` which allow some customisations to be made.
To see an example of all the implementation properties please take a look at the [test.html](./test.html) file provided
in this repo.
@@ -329,6 +329,118 @@ Sometimes it's useful to group operations together to explain what they are doin
---
+## Making a payment
+
+To make a payment using Simple Signer, you need to provide the necessary parameters either through the URL or using the `postMessage` method. Follow the steps below to integrate the payment functionality into your web application:
+
+### Step 1: Specify Payment Parameters
+
+You can pass the payment parameters such as the receiver's account, amount, asset type, and issuer through the URL or `postMessage` method. Here's an example of how you can do it:
+
+```html
+
+
+
+
+
+ Simple Signer - Make Payment Demo
+
+
+
+
+ Make Payment
+
+ This page demonstrates the process of making a payment using the
+ Simple Signer.
+
+ Usage
+ Click the "Make Payment" button to initiate the payment process.
+ Make Payment
+
+
+
+
+```
+
+You may choose to pass the payment parameters to Simple Signer either via URL or via postMessage.
+
+Via URL:
+
+```javascript
+const receiver = 'Receiver public key';
+const amount = '10';
+const assetCode = 'native'; // 'native' for XLM, or asset code for other assets
+const issuer = ''; // If assetCode is not 'native', provide issuer's public key
+
+const paymentWindow = window.open(
+ `https://sign.scalemote.io/payment/?receiver=${receiver}&amount=${amount}&assetCode=${assetCode}&issuer=${issuer}`,
+ 'Payment_Window',
+ 'width=360, height=700',
+);
+```
+
+Via PostMessage:
+
+Post Message has some advantages over the URL method which are covered in the Payment API section.
+
+```javascript
+const receiver = 'Receiver public key';
+const amount = '10';
+const assetCode = 'native'; // 'native' for XLM, or asset code for other assets
+const issuer = ''; // If assetCode is not 'native', provide issuer's public key
+
+const simpleSignerUrl = 'https://sign.scalemote.io';
+const paymentWindow = window.open(
+ `${simpleSignerUrl}/payment`,
+ 'Payment_Window',
+ 'width=360, height=700',
+);
+
+window.addEventListener('message', (e) => {
+ if (
+ e.origin !== simpleSignerUrl &&
+ e.data.type === 'onReady' &&
+ e.data.page === 'payment'
+ ) {
+ paymentWindow.postMessage(
+ { receiver, amount, assetCode, issuer },
+ simpleSignerUrl,
+ );
+ }
+});
+```
+
## Language selection
By default, Simple Signer will detect the browser's language and serve Simple Signer using this configuration. If the
diff --git a/cypress/fixtures/payment.json b/cypress/fixtures/payment.json
new file mode 100644
index 00000000..5b698c57
--- /dev/null
+++ b/cypress/fixtures/payment.json
@@ -0,0 +1,6 @@
+{
+ "destinationAccount": "GCECUYXE32PPYKPVYOSILACOCUGMEZSDO7GYERC2GKAG2HM34BLMMOMB",
+ "amountToSend": 100,
+ "assetCode": "native",
+ "issuer": "none"
+}
diff --git a/cypress/integration/ui/events_spec.ts b/cypress/integration/ui/events_spec.ts
index 00c43995..2fcb8263 100644
--- a/cypress/integration/ui/events_spec.ts
+++ b/cypress/integration/ui/events_spec.ts
@@ -1,6 +1,7 @@
///
///
import { operationsXdr } from '../../fixtures/operations.json';
+import { amountToSend, assetCode, destinationAccount, issuer } from '../../fixtures/payment.json';
const operationGroupTitle = 'Payment';
const operationGroupDescription = 'This is a merge account operation';
@@ -37,4 +38,17 @@ describe('Events', () => {
cy.get('.tx-operation-container').should('have.length', 2);
cy.get('.tx-description-container').contains(operationDescription);
});
+
+ it('should render a payment operation', () => {
+ cy.visit('/payment');
+ cy.window().then((win) => {
+ win.postMessage({
+ receiver: destinationAccount,
+ issuer,
+ amount: amountToSend,
+ assetCode,
+ });
+ });
+ cy.get('.receiver').should('have.length', 1);
+ });
});
diff --git a/cypress/integration/ui/logout_spec.ts b/cypress/integration/ui/logout_spec.ts
index 73d9bc08..05714cbd 100644
--- a/cypress/integration/ui/logout_spec.ts
+++ b/cypress/integration/ui/logout_spec.ts
@@ -22,4 +22,13 @@ describe('logout', () => {
cy.get('.logout-active').contains('Logout').click();
cy.url().should('include', '/connect');
});
+
+ it('Should logout on /payment', () => {
+ cy.visit('/payment');
+ window.localStorage.setItem('wallet', 'xbull');
+ cy.wait(5000);
+ cy.get('.logout-button').click();
+ cy.get('.logout-active').contains('Logout').click();
+ cy.url().should('include', '/connect');
+ });
});
diff --git a/cypress/integration/ui/payment_spec.ts b/cypress/integration/ui/payment_spec.ts
new file mode 100644
index 00000000..fa1423e0
--- /dev/null
+++ b/cypress/integration/ui/payment_spec.ts
@@ -0,0 +1,29 @@
+///
+///
+import { amountToSend, assetCode, destinationAccount, issuer } from '../../fixtures/payment.json';
+
+describe('checks that the /payment component works', () => {
+ const BASE_URL = '/payment';
+
+ it('should visit /payment with payment information but user is not connected', () => {
+ cy.visit(
+ `${BASE_URL}?receiver=${destinationAccount}&amount=${amountToSend}&assetCode=${assetCode}&issuer=${issuer}`,
+ );
+ cy.get('.user-not-connected').contains('User is not connected');
+ cy.get('.payment-btn').click();
+ cy.url().should('include', '/connect');
+ });
+
+ it('should render payment information if payment parameters are valid', () => {
+ window.localStorage.setItem('wallet', 'xbull');
+ cy.visit(
+ `${BASE_URL}?receiver=${destinationAccount}&amount=${amountToSend}&assetCode=${assetCode}&issuer=${issuer}`,
+ );
+ cy.get('.simple-signer').contains(`You are paying ${amountToSend} XLM to the account ${destinationAccount}.`);
+ });
+
+ it('should render an error if payment parameters was not provided', () => {
+ cy.visit(`${BASE_URL}?receiver=${destinationAccount}`);
+ cy.get('.simple-signer').contains("Sorry, the recipient's data wasn't provided.");
+ });
+});
diff --git a/package-lock.json b/package-lock.json
index 43248c9f..372ccf9e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1739,14 +1739,6 @@
"sodium-native": "^4.0.1"
}
},
- "node_modules/@stellar/stellar-base/node_modules/bignumber.js": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
- "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
- "engines": {
- "node": "*"
- }
- },
"node_modules/@stellar/stellar-base/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -1770,16 +1762,6 @@
"ieee754": "^1.2.1"
}
},
- "node_modules/@stellar/stellar-base/node_modules/sodium-native": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.4.tgz",
- "integrity": "sha512-faqOKw4WQKK7r/ybn6Lqo1F9+L5T6NlBJJYvpxbZPetpWylUVqz449mvlwIBKBqxEHbWakWuOlUt8J3Qpc4sWw==",
- "hasInstallScript": true,
- "optional": true,
- "dependencies": {
- "node-gyp-build": "^4.6.0"
- }
- },
"node_modules/@sveltejs/vite-plugin-svelte": {
"version": "1.0.0-next.44",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.0.0-next.44.tgz",
@@ -3155,6 +3137,34 @@
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
"dev": true
},
+ "node_modules/axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/axios/node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/axios/node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
"node_modules/babel-jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@@ -3304,6 +3314,14 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
+ "node_modules/bignumber.js": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
+ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -9341,6 +9359,16 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/sodium-native": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.6.tgz",
+ "integrity": "sha512-uYsyycwcz9kYDwpXxJmL2YZosynsxcP6RPySbARVJdC9uNDa2CMjzJ7/WsMMvThKgvAYsBWdZc7L/WSVj9lTcA==",
+ "hasInstallScript": true,
+ "optional": true,
+ "dependencies": {
+ "node-gyp-build": "^4.6.0"
+ }
+ },
"node_modules/sonic-boom": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz",
@@ -9506,42 +9534,6 @@
"urijs": "^1.19.1"
}
},
- "node_modules/stellar-sdk/node_modules/axios": {
- "version": "1.6.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz",
- "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==",
- "dependencies": {
- "follow-redirects": "^1.15.4",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/stellar-sdk/node_modules/bignumber.js": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
- "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/stellar-sdk/node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/stellar-sdk/node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
- },
"node_modules/stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
@@ -12076,11 +12068,6 @@
"tweetnacl": "^1.0.3"
},
"dependencies": {
- "bignumber.js": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
- "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="
- },
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -12089,15 +12076,6 @@
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
- },
- "sodium-native": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.4.tgz",
- "integrity": "sha512-faqOKw4WQKK7r/ybn6Lqo1F9+L5T6NlBJJYvpxbZPetpWylUVqz449mvlwIBKBqxEHbWakWuOlUt8J3Qpc4sWw==",
- "optional": true,
- "requires": {
- "node-gyp-build": "^4.6.0"
- }
}
}
},
@@ -13220,6 +13198,33 @@
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
"dev": true
},
+ "axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "requires": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ },
+ "dependencies": {
+ "form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ }
+ }
+ },
"babel-jest": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz",
@@ -13330,6 +13335,11 @@
}
}
},
+ "bignumber.js": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
+ "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="
+ },
"binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -17754,6 +17764,15 @@
}
}
},
+ "sodium-native": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.0.6.tgz",
+ "integrity": "sha512-uYsyycwcz9kYDwpXxJmL2YZosynsxcP6RPySbARVJdC9uNDa2CMjzJ7/WsMMvThKgvAYsBWdZc7L/WSVj9lTcA==",
+ "optional": true,
+ "requires": {
+ "node-gyp-build": "^4.6.0"
+ }
+ },
"sonic-boom": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz",
@@ -17890,38 +17909,6 @@
"randombytes": "^2.1.0",
"toml": "^3.0.0",
"urijs": "^1.19.1"
- },
- "dependencies": {
- "axios": {
- "version": "1.6.4",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.4.tgz",
- "integrity": "sha512-heJnIs6N4aa1eSthhN9M5ioILu8Wi8vmQW9iHQ9NUvfkJb0lEEDUiIdQNAuBtfUt3FxReaKdpQA5DbmMOqzF/A==",
- "requires": {
- "follow-redirects": "^1.15.4",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "bignumber.js": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
- "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug=="
- },
- "form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- }
- },
- "proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
- }
}
},
"stream-shift": {
diff --git a/src/App.svelte b/src/App.svelte
index 42c15fc7..557a8b12 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -6,6 +6,7 @@
import { WalletConnectService } from './lib/service/walletConnect';
import Home from './routes/Home.svelte';
import Connect from './routes/connect/Connect.svelte';
+ import Payment from './routes/payment/Payment.svelte';
import Sign from './routes/sign/Sign.svelte';
import { detectedLanguage, isLanguageLoading, walletConnectClient } from './store/global';
@@ -28,6 +29,7 @@
+
{/if}
diff --git a/src/constants.ts b/src/constants.ts
index c845590d..94d6c471 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -3,6 +3,7 @@ const {
VITE_DAPP_BASE_URL: DAPP_BASE_URL,
VITE_HORIZON_NETWORK_PASSPHRASE: HORIZON_NETWORK_PASSPHRASE,
VITE_STELLAR_NETWORK: STELLAR_NETWORK,
+ VITE_HORIZON_URL: HORIZON_URL,
} = import.meta.env;
-export { PROJECT_ID_FOR_WALLET_CONNECT, DAPP_BASE_URL, HORIZON_NETWORK_PASSPHRASE, STELLAR_NETWORK };
+export { PROJECT_ID_FOR_WALLET_CONNECT, DAPP_BASE_URL, HORIZON_NETWORK_PASSPHRASE, STELLAR_NETWORK, HORIZON_URL };
diff --git a/src/env.d.ts b/src/env.d.ts
index bc9bb645..daf5b676 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -5,6 +5,7 @@ interface ImportMetaEnv {
readonly VITE_STELLAR_NETWORK: string;
readonly VITE_PROJECT_ID_FOR_WALLET_CONNECT: string;
readonly VITE_DAPP_BASE_URL: string;
+ readonly VITE_HORIZON_URL: string;
}
interface ImportMeta {
diff --git a/src/lib/bridge/Bridge.ts b/src/lib/bridge/Bridge.ts
index 17311ce4..3a11a009 100644
--- a/src/lib/bridge/Bridge.ts
+++ b/src/lib/bridge/Bridge.ts
@@ -1,21 +1,25 @@
import EventFactory from './EventFactory';
import type ISimpleSignerEvent from './ISimpleSignerEvent';
import type IAvailableWalletsMessage from './availableWalletsMessage/IAvailableWalletsMessage';
+import type { IPaymentMessage } from './paymentMessage/IPaymentMessage';
import type { ITransactionMessage } from './transactionMessage/ITransactionMessage';
export type IAvailableWalletsMessageHandler = (message: IAvailableWalletsMessage) => void;
export type ITransactionMessageHandler = (message: ITransactionMessage) => void;
+export type IPaymentMessageHandler = (message: IPaymentMessage) => void;
export enum SimpleSignerEventType {
ON_CONNECT = 'onConnect',
ON_READY = 'onReady',
ON_SIGN = 'onSign',
ON_CANCEL = 'onCancel',
+ ON_PAYMENT = 'onPayment',
}
export enum SimpleSignerPageType {
CONNECT = 'connect',
SIGN = 'sign',
+ PAYMENT = 'payment',
}
export default class Bridge {
@@ -31,6 +35,7 @@ export default class Bridge {
}
private availableWalletsMessageHandlers: IAvailableWalletsMessageHandler[] = [];
private transactionMessageHandlers: ITransactionMessageHandler[] = [];
+ private paymentMessageHandlers: IPaymentMessageHandler[] = [];
public sendSignedTx(signedXDR: string) {
this.mainActionPerformed = true;
@@ -49,7 +54,6 @@ export default class Bridge {
public sendOnConnectEvent(publicKey: string, wallet: string): void {
this.mainActionPerformed = true;
this.sendMessage(EventFactory.createOnConnectEvent(publicKey, wallet));
- this.closeWindow();
}
public addAvailableWalletsMessageHandler(handler: IAvailableWalletsMessageHandler) {
@@ -60,6 +64,10 @@ export default class Bridge {
this.transactionMessageHandlers.push(handler);
}
+ public addPaymentMessageHandler(handler: IPaymentMessageHandler) {
+ this.paymentMessageHandlers.push(handler);
+ }
+
public getTransactionMessageFromUrl(queryString?: string): ITransactionMessage | null {
const urlParams = new URLSearchParams(queryString || window.location.search);
const xdrParam = urlParams.get('xdr');
@@ -77,12 +85,37 @@ export default class Bridge {
}
}
+ public getPaymentMessageFromUrl(queryString?: string): IPaymentMessage | null {
+ const urlParams = new URLSearchParams(queryString || window.location.search);
+ const receiver = urlParams.get('receiver');
+ const amount = urlParams.get('amount');
+ const assetCode = urlParams.get('assetCode');
+ const issuer = urlParams.get('issuer');
+
+ if (receiver && amount && assetCode && issuer) {
+ return {
+ receiver,
+ amount,
+ assetCode,
+ issuer,
+ };
+ } else {
+ return null;
+ }
+ }
+
public getWalletsFromUrl(): string[] {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
return urlParams.getAll('wallets');
}
+ public getRedirectFromUrl(): string | null {
+ const queryString = window.location.search;
+ const urlParams = new URLSearchParams(queryString);
+ return urlParams.get('redirect');
+ }
+
private messageHandler(e: MessageEvent): void {
if ('wallets' in e.data) {
const message = e.data as IAvailableWalletsMessage;
@@ -95,9 +128,15 @@ export default class Bridge {
this.transactionMessageHandlers.forEach((handler) => handler(message));
return;
}
+
+ if ('receiver' in e.data && 'amount' in e.data && 'assetCode' in e.data && 'issuer' in e.data) {
+ const message = e.data as IPaymentMessage;
+ this.paymentMessageHandlers.forEach((handler) => handler(message));
+ return;
+ }
}
- private closeWindow() {
+ public closeWindow() {
return window.close();
}
diff --git a/src/lib/bridge/EventFactory.ts b/src/lib/bridge/EventFactory.ts
index c9de5c77..8925d63f 100644
--- a/src/lib/bridge/EventFactory.ts
+++ b/src/lib/bridge/EventFactory.ts
@@ -38,4 +38,22 @@ export default class EventFactory {
page: SimpleSignerPageType.SIGN,
};
}
+
+ static createOnPaymentRequest(
+ receiver: string,
+ amount: number,
+ assetCode: string,
+ issuer: string,
+ ): ISimpleSignerEvent {
+ return {
+ type: SimpleSignerEventType.ON_PAYMENT,
+ message: {
+ receiver,
+ amount,
+ assetCode,
+ issuer,
+ },
+ page: SimpleSignerPageType.PAYMENT,
+ };
+ }
}
diff --git a/src/lib/bridge/paymentMessage/IPaymentMessage.ts b/src/lib/bridge/paymentMessage/IPaymentMessage.ts
new file mode 100644
index 00000000..592d9ca3
--- /dev/null
+++ b/src/lib/bridge/paymentMessage/IPaymentMessage.ts
@@ -0,0 +1,6 @@
+export interface IPaymentMessage {
+ receiver: string;
+ amount: string;
+ assetCode: string;
+ issuer: string;
+}
diff --git a/src/lib/i18n/ITranslation.ts b/src/lib/i18n/ITranslation.ts
index 1757752c..34482f11 100644
--- a/src/lib/i18n/ITranslation.ts
+++ b/src/lib/i18n/ITranslation.ts
@@ -30,8 +30,10 @@ export interface ITranslation {
ENGLISH_ISO: string;
ENGLISH: string;
ERROR: string;
+ ERROR_MISSING_RECEIVER_DATA: string;
EXPAND_ALL: string;
EXTEND_TO: string;
+ FAILED_PAYMENT: string;
FEE_BUMP_DESCRIPTION_1: string;
FEE_BUMP_DESCRIPTION_2: string;
FEE_BUMP: string;
@@ -40,6 +42,7 @@ export interface ITranslation {
FUNCTION_TYPE: string;
GO_TO_CONNECT: string;
GO_TO_SIGN: string;
+ GO_TO_PAYMENT: string;
HIDE_ALL: string;
HIDE_KEY: string;
HIGH_THRESHOLD: string;
@@ -108,6 +111,7 @@ export interface ITranslation {
OPERATION: string;
OPERATIONS_LIST: string;
PATH: string;
+ PAY: string;
PREAUTH_TX: string;
PRICE: string;
PRIVATE_KEY: string;
@@ -129,7 +133,9 @@ export interface ITranslation {
SPANISH: string;
SPONSORED_ID: string;
STARTING_BALANCE: string;
+ SUCCESSFUL_PAYMENT: string;
TIME_BOUNDS: string;
+ TO_THE_ACCOUNT: string;
TRANSACTION: string;
TRUSTOR: string;
USER_IS_NOT_CONNECTED: string;
@@ -137,5 +143,6 @@ export interface ITranslation {
WEIGHT: string;
XDR_INVALID: string;
XDR_NOT_PROVIDED: string;
+ YOU_ARE_PAYING: string;
YOUR_ACCOUNT: string;
}
diff --git a/src/lib/i18n/languages/english.json b/src/lib/i18n/languages/english.json
index b3cde2cb..c86035d9 100644
--- a/src/lib/i18n/languages/english.json
+++ b/src/lib/i18n/languages/english.json
@@ -30,8 +30,10 @@
"ENGLISH_ISO": "en",
"ENGLISH": "English",
"ERROR": "Error",
+ "ERROR_MISSING_RECEIVER_DATA": "Sorry, the recipient's data wasn't provided.",
"EXPAND_ALL": "Expand all",
"EXTEND_TO": "Extend to:",
+ "FAILED_PAYMENT": "Payment failed. Please try again.",
"FEE_BUMP_DESCRIPTION_1": "You will sign to pay the",
"FEE_BUMP_DESCRIPTION_2": "of the transaction below",
"FEE_BUMP": "FEE BUMP",
@@ -40,6 +42,7 @@
"FUNCTION_TYPE": "Function type:",
"GO_TO_CONNECT": "Go to Connect",
"GO_TO_SIGN": "Go to Sign",
+ "GO_TO_PAYMENT": "Go to Payment",
"HIDE_ALL": "Hide all",
"HIDE_KEY": "Hide key",
"HIGH_THRESHOLD": "High Threshold:",
@@ -108,6 +111,7 @@
"OPERATION": "Operation:",
"OPERATIONS_LIST": "Operations list",
"PATH": "Path:",
+ "PAY": "Pay",
"PREAUTH_TX": "preAuthTx:",
"PRICE": "Price:",
"PRIVATE_KEY": "Private Key",
@@ -129,7 +133,9 @@
"SPANISH": "Spanish",
"SPONSORED_ID": "Sponsored ID:",
"STARTING_BALANCE": "Starting Balance:",
+ "SUCCESSFUL_PAYMENT": "The payment was successful!",
"TIME_BOUNDS": "Time Bounds:",
+ "TO_THE_ACCOUNT": "to the account",
"TRANSACTION": "Transaction:",
"TRUSTOR": "Trustor:",
"USER_IS_NOT_CONNECTED": "User is not connected",
@@ -137,5 +143,6 @@
"WEIGHT": "Weight:",
"XDR_INVALID": "Sorry, the XDR is invalid",
"XDR_NOT_PROVIDED": "Sorry, an XDR wasn't provided",
+ "YOU_ARE_PAYING": "You are paying",
"YOUR_ACCOUNT": "Your Account"
}
diff --git a/src/lib/i18n/languages/spanish.json b/src/lib/i18n/languages/spanish.json
index ee97a441..c878f673 100644
--- a/src/lib/i18n/languages/spanish.json
+++ b/src/lib/i18n/languages/spanish.json
@@ -30,8 +30,10 @@
"ENGLISH_ISO": "en",
"ENGLISH": "Inglés",
"ERROR": "Error",
+ "ERROR_MISSING_RECEIVER_DATA": "Lo sentimos, la información del destinatario no fue proporcionada.",
"EXPAND_ALL": "Expandir todas",
"EXTEND_TO": "Extender a:",
+ "FAILED_PAYMENT": "Pago fallido. Por favor, inténtelo de nuevo.",
"FEE_BUMP_DESCRIPTION_1": "Vas a firmar para pagar la",
"FEE_BUMP_DESCRIPTION_2": "de la transacción a continuación",
"FEE_BUMP": "FEE BUMP",
@@ -40,6 +42,7 @@
"FUNCTION_TYPE": "Tipo de función:",
"GO_TO_CONNECT": "Ir a Conectar",
"GO_TO_SIGN": "Ir a Firmar",
+ "GO_TO_PAYMENT": "Ir a Pagar",
"HIDE_ALL": "Ocultar todas",
"HIDE_KEY": "Ocultar llave",
"HIGH_THRESHOLD": "Umbral alto:",
@@ -108,6 +111,7 @@
"OPERATION": "Operación:",
"OPERATIONS_LIST": "Lista de operaciones",
"PATH": "Ruta:",
+ "PAY": "Pagar",
"PREAUTH_TX": "preAuthTx:",
"PRICE": "Precio:",
"PRIVATE_KEY": "Llave Privada",
@@ -129,7 +133,9 @@
"SPANISH": "Español",
"SPONSORED_ID": "ID Patrocinada:",
"STARTING_BALANCE": "Balance inicial:",
+ "SUCCESSFUL_PAYMENT": "¡El pago ha sido exitoso!",
"TIME_BOUNDS": "Límites de tiempo:",
+ "TO_THE_ACCOUNT": "a la cuenta",
"TRANSACTION": "Transacción:",
"TRUSTOR": "Fideicomitente:",
"USER_IS_NOT_CONNECTED": "El usuario no está conectado",
@@ -137,5 +143,6 @@
"WEIGHT": "Peso:",
"XDR_INVALID": "Lo sentimos, el XDR es inválido",
"XDR_NOT_PROVIDED": "Lo sentimos, no encontramos ningún XDR",
+ "YOU_ARE_PAYING": "Estás pagando",
"YOUR_ACCOUNT": "Tu Cuenta"
}
diff --git a/src/lib/stellar/Payment.ts b/src/lib/stellar/Payment.ts
new file mode 100644
index 00000000..d0035647
--- /dev/null
+++ b/src/lib/stellar/Payment.ts
@@ -0,0 +1,33 @@
+import { Asset, BASE_FEE, Operation, TransactionBuilder } from 'stellar-sdk';
+
+import { CURRENT_NETWORK_PASSPHRASE } from './StellarNetwork';
+import { server } from './utils';
+
+export async function createPaymentTransaction(
+ publicKey: string,
+ receiver: string,
+ amount: string,
+ assetCode: string,
+ issuer?: string,
+) {
+ const asset = assetCode === 'native' ? Asset.native() : new Asset(assetCode, issuer);
+
+ try {
+ const account = await server.loadAccount(publicKey);
+ return new TransactionBuilder(account, {
+ fee: BASE_FEE,
+ networkPassphrase: CURRENT_NETWORK_PASSPHRASE,
+ })
+ .addOperation(
+ Operation.payment({
+ destination: receiver,
+ asset: asset,
+ amount: amount,
+ }),
+ )
+ .setTimeout(30)
+ .build();
+ } catch (error) {
+ throw new Error(JSON.stringify(error));
+ }
+}
diff --git a/src/lib/stellar/utils/index.ts b/src/lib/stellar/utils/index.ts
new file mode 100644
index 00000000..2f3c7eb8
--- /dev/null
+++ b/src/lib/stellar/utils/index.ts
@@ -0,0 +1,5 @@
+import { Horizon } from 'stellar-sdk';
+
+import { HORIZON_URL } from '../../../constants';
+
+export const server = new Horizon.Server(HORIZON_URL);
diff --git a/src/lib/wallets/privateKey/PrivateKey.ts b/src/lib/wallets/privateKey/PrivateKey.ts
index f3729e89..5692067a 100644
--- a/src/lib/wallets/privateKey/PrivateKey.ts
+++ b/src/lib/wallets/privateKey/PrivateKey.ts
@@ -17,13 +17,15 @@ export default class PrivateKey extends AbstractWallet implements IWallet {
private PRIVATE_KEY_ITEM_NAME = 'privateKey';
private INITIALIZATION_VECTORS_KEY_ITEM_NAME = 'iv';
- public override async getPublicKey(privateKey: string): Promise {
+ public override async getPublicKey(privateKey?: string): Promise {
let publicKey: string;
try {
- publicKey = Keypair.fromSecret(privateKey).publicKey();
- super.persistWallet();
+ if (privateKey) {
+ publicKey = Keypair.fromSecret(privateKey).publicKey();
+ super.persistWallet();
- await this.storeEncryptedPrivateKey(privateKey);
+ await this.storeEncryptedPrivateKey(privateKey);
+ }
} catch (e) {
if (e instanceof InvalidPrivateKeyError) {
console.error('Invalid key, please try again');
diff --git a/src/routes/Home.svelte b/src/routes/Home.svelte
index f550e3a7..66ee2d4f 100644
--- a/src/routes/Home.svelte
+++ b/src/routes/Home.svelte
@@ -12,4 +12,7 @@
{$language.GO_TO_SIGN}
+
+ {$language.GO_TO_PAYMENT}
+
diff --git a/src/routes/connect/Connect.svelte b/src/routes/connect/Connect.svelte
index f6790869..d78ce89e 100644
--- a/src/routes/connect/Connect.svelte
+++ b/src/routes/connect/Connect.svelte
@@ -11,6 +11,7 @@
const parent = window.opener;
const bridge = new Bridge(SimpleSignerPageType.CONNECT);
+ const redirect = bridge.getRedirectFromUrl();
$wallets = bridge.getWalletsFromUrl();
if (parent && !$wallets.length) {
@@ -26,6 +27,11 @@
const publicKey: string = detail.publicKey;
const wallet: IWallet = detail.wallet;
bridge.sendOnConnectEvent(publicKey, wallet.getName());
+ if (redirect) {
+ window.location.href = `/${redirect}`;
+ } else {
+ bridge.closeWindow();
+ }
}
bridge.sendOnReadyEvent();
diff --git a/src/routes/payment/Payment.svelte b/src/routes/payment/Payment.svelte
new file mode 100644
index 00000000..e69487f2
--- /dev/null
+++ b/src/routes/payment/Payment.svelte
@@ -0,0 +1,309 @@
+
+
+{#if paymentResultMessage}
+
+
{paymentResultMessage}
+
{$language.CLOSE}
+
+{:else}
+
+ {#if !receiver || !amount || !assetCode || !issuer}
+
{$language.ERROR}
+
+ {:else}
+
{$language.PAY}
+
+
+
{$language.NETWORK}:
+
{CURRENT_STELLAR_NETWORK}
+
+
+
+ {$language.YOU_ARE_PAYING}
+ {amount}
+ {assetCode === 'native' ? 'XLM' : { assetCode }}
+ {$language.TO_THE_ACCOUNT}
+
+ {receiver}.
+
+
+ {#if wallet}
+
+ handlePopupClose()}
+ disabled={isPaymentInProgress}
+ >
+ {$language.CANCEL}
+
+ {#if isPaymentInProgress}
+
+ {:else}
+ {$language.PAY}
+ {/if}
+
+
+ {:else}
+
+ {/if}
+ {/if}
+
+{/if}
+
+
diff --git a/test.html b/test.html
index 1c3244d0..81a1fea6 100644
--- a/test.html
+++ b/test.html
@@ -16,13 +16,20 @@
Connect
Open sign via URL
Open sign via postMessage
+ Open payment via URL
+ Open payment via postMessage