Understand more about the innovative functionality of TokenPay and get to know different integration and whitelabeling options.
The innovative TokenPay smart contracts solve known structural problems of decentralised exchange processes with regard to arbitrage and exchange rate risks while making optimal use of the available liquidity. Predominantly based on known liquidity pool schemes, these smart contracts form the core of the TokenPay technology, which is extended by various decentralised systems such as bridges and decentralised exchanges. Existing verified smart contracts are always used for continuous monitoring of the market situation and extended decentralised liquidity management.
TokenPay technically manages to completely remove the exchange rate risk from the transaction process. The technical solution involves new smart contracts and a special way of linking transactions.
TokenPay's new smart contracts allow a different recipient address to be specified alternately in a cross-hierarchy liquidity pool transaction. This means that the result of the liquidity pool swap, the stablecoins, are sent directly to the merchant's wallet address.
The new liquidity pool implementation and the special broadcasting of transactions to the nodes make it possible to execute several transactions directly one after the other within a block, so that the exchange rate of the intermediary used cannot technically change between the two transactions.
TokenPay uses technical measures to remove arbitrage from the transaction process. Utility tokens, which measure the circulation of different assets more efficiently, and decentralized liquidity pools with integrated market-making algorithms are combined for this purpose.
TokenPay's innovative multi-level pool structure enables the respective currencies in a pool to be balanced against each other via a higher-level pool. This balancing takes place on the basis of the transactions that have taken place and thus simulates the function of a central bank in traditional currency markets. In addition, oracles, bridging and DEX protocols are included in order to utilise the available liquidity as efficiently as possible.
Variables cannot simply be stored on the blockchain. That is why we have created various utility tokens in the background that enable the individual smart contracts to read a circulating balance and a change in the intermediary over time in a decentralized manner using the account status of the respective utility tokens. Based on this, the cash flow of different currencies and stablecoins can be balanced without an external authority and the associated integrity risks.
TokenPay does not have access to third-party funds at any time, either during, before or after a transaction, nor does it receive any special transaction fees.
TokenPay does not know any personal transaction data, no purchase history and only refers to the public data of the blockchain, as well as customer data provided by the customer.
TokenPay can neither prevent transactions nor reverse transactions. In principle, TokenPay has no direct influence on transactions, senders or recipients and the currencies used.
Currently, for TokenPay E-Commerce plug-ins for the shop-systems WordPress/WooCommerce and Shopware are available.
In addition, a plug-in for the Shopify online store modular system is currently under development. For integration, you need access to the administration interface of the respective modular system. Please request the required plug-in via the contact form.
For integration, you need access to the administration interface of the respective modular system.
Please request the required plug-in via the contact form.
Similar to other payment solutions, TokenPay requires a connection to your online store and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalizing the payment, the end customer is redirected to a customizable page.
For WordPress:
Make sure that WooCommerce is installed and set up on your WordPress site. Upload the ZIP source code file that you received from our support team as a new plug-in. Confirm the installation and activate the plug-in. You can then activate the payment method under WooCommerce / Settings / Payments / TokenPay and customize the title and description text displayed on your checkout page. Save your changes.
Also copy the displayed webhook. This must then be linked to the corporate customer dashboard.
For Shopware:
Open the Shopware admin panel. To access it, it may be necessary to open a separate browser window and clear your cache. Upload the ZIP source code file you received from our support team as a new plug-in. Confirm the installation and activate the plug-in.
You will then find the “Payments and shipping” section under the “Storefront” settings. Activate the TokenPay payment gateway here.
Then navigate to Settings / Payment method and activate the gateway as well. Here you can also view the current webhook under “Technical details”. This must then be linked to the corporate customer dashboard. Save your changes.
The access key is a unique key that is required to generate checkout sessions. This is uniquely assigned to your corporate customer account.
Ask an authorized representative or a person authorized by the representative to create a corporate customer account for TokenPay. The publicly visible settings for configuring the checkout session must then be completed under “Settings” (top right). It is mandatory to upload the complete imprint information as well as a company logo and a background color to individualize the checkout sessions. In addition, the redirect URLs for a successful and a failed payment can be defined here. To redirect to the automatically generated “Thank You” pages of the modular systems, it is usually sufficient to enter the basic URL of the online store. In the last step, paste the copied endpoint URL under “Success URL” and “Error URL” - this is identical for both fields. Then save your data. You can now copy your access key.
Now enter the access key on the corresponding settings pages of your modular system. From now on it is possible to accept e-commerce payments with TokenPay.
A RESTful API is available for integrating TokenPay into individual online stores.
Similar to other payment solutions, TokenPay requires a connection to your online store and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalizing the payment, the end customer is redirected to a customizable page.
The API provided offers a detailed mechanism for accepting crypto payments, verifying them and responding to different scenarios. One of the main features of this payment infrastructure is the use of webhooks to forward the transaction status to the online store's server.
When a webhook is triggered, it sends a POST request to the configured endpoint with a JSON response.
/*
Webhook Types:
payment_success:
Triggered when a payment has been successfully verified.
payment_failed:
Triggered when a payment verification fails, or when there's an issue with the payment.
Events:
Payment Success:
type: Always`payment_success`
customId: Custom identifier for the session or order
Payment Failed:
type: Always `payment_failed`
customId: Custom identifier for the session or order
*/
Best practices for setting up webhooks:
The access key is a unique key that is required to generate checkout sessions. This is uniquely assigned to your corporate customer account.
Ask an authorised representative or a person authorised by them to create a corporate customer account for TokenPay. The publicly visible settings for configuring the checkout session must then be completed under ‘Settings’ (top right). It is mandatory to upload the complete imprint information as well as a company logo and a background colour to customise the checkout sessions. The redirect URLs for a successful and a failed payment can also be defined here. In the last step, add the created webhook endpoint URLs under ‘Success URL’ and ‘Error URL’ - it is also possible to specify the same endpoint for both event types. Then save your data. You can now copy your access key / API endpoint.
The ‘createCheckoutSession’ function serves as the end point for creating one-off checkout sessions.
/*
Parameters:
- req (Object): The incoming request object.
- req.query.accessKey (String): A UUID4 key to access the checkout configuration.
- req.body.items (Array): An array of items for the checkout session. This is mandatory and must adhere to the specified format.
- req.body.customId (String): An optional custom identifier for the checkout session.
- req.body.successUrl (String): An optional URL where the user is redirected upon successful checkout.
- res (Object): The response object used to send responses back to the caller.
- next (Function): Express middleware function to pass control to the next handler.
Items Format:
Items provided in the req.body.items should be an array of objects with the following format:
items: [
{
name: "Special Item", // Item name
price: 0.001, // Item price
quantity: 1 // Quantity of the item
},
{
name: "Another Item",
price: 0.001,
quantity: 1
}
]
Response Format
The successful response will contain:
- url (String): A URL redirecting to the frontend checkout page with the session's ID.
- session (Object): An object containing:
- id (String): The session's ID.
- total (Number): The total amount for the checkout session.
- items (Array): The items for the checkout session.
- paymentStatus (String): The payment status for the checkout session.
- customId (String): An optional custom identifier for the checkout session.
- successUrl (String): The URL where the user is redirected upon a successful checkout.
Errors and Status Codes
The function can return various HTTP status codes and errors:
- 400: For invalid access keys, invalid items format or count, items missing required fields, invalid `successUrl` formats.
- 404: When the provided `accessKey` does not match any existing checkout configurations.
- 500: If any internal server errors occur.
Example Usage
Initiate a checkout session by sending a `POST` request to the endpoint where this function is mounted, with the required parameters in the body and query string.
Example Request:
POST https://BASE_URL/checkoutSession/V1/createCheckoutSession?accessKey=YOUR_ACCESS_KEY
{
"items": [
{
"name": "Special Item",
"price": 0.001,
"quantity": 1
},
{
"name": "Another Item",
"price": 0.001,
"quantity": 1
}
],
"customId": "YOUR_CUSTOM_ID",
"successUrl": "http://example.com/success"
}
Example Response:
{
"url": "https://BASE_URL/SESSION_ID",
"session": {
"id": "SESSION_ID",
"total": 0.002,
"items": [...],
"paymentStatus": "pending",
"customId": "YOUR_CUSTOM_ID",
"successUrl": "http://example.com/success"
}
}
*/
A RESTful API is available for integrating TokenPay into individual online stores.
TokenPay Point of Sale is available as a static QR code solution or as a dynamic QR code solution in conjunction with an end device of your choice on site. In order to achieve the best possible efficiency and user experience on site, we are happy to customise the interfaces and user interfaces according to your needs.
We are also happy to provide an interface to your checkout systems or an individualised overview on your end devices on site. It is also possible to customise the checkout sessions and add survey elements, for example. Get in touch now to find out more about the possibilities.
TokenPay uses the Polygon blockchain to bundle all data and settle most transactions. Other blockchains are connected via bridges.
Similar to other payment solutions, TokenPay requires a connection to your online shop and the corporate customer dashboard. During the payment process, the end customer is redirected from the checkout page to a unique payment page hosted on https://checkout.usetokenpay.com. After finalising the payment, the end customer is redirected to an individually configurable page. This process is also used if you integrate TokenPay Blockchain into your central interface.
It is also possible to integrate TokenPay Blockchain directly at smart contract level. In this case, integration on the Polygon blockchain is necessary. If your project is not or not primarily active on Polygon, it is also possible to set up interchain messaging protocols that interact with the emitted events of the TokenPay smart contracts. This means that TokenPay Blockchain remains open to all technologies within the blockchain world. We are also happy to support you in developing a customised solution.
You can find the current TokenPay Factory Contract here.
You can find the current TokenPay router contract here.
You can find the current intermediary token contract here.
Please note that all deployment addresses refer to Polygon mainnet.
Further routings, calculations and integrations of partners take place centrally and are confirmed individually by the end users in the authorisation requests with the respective wallets.
The integration of TokenPay Blockchain into central systems is analogous to the integration of TokenPay E-Commerce.
The integration of TokenPay smart contracts can be done flexibly according to your needs. Below you will find the code reference of the current TokenPay SwapRouter entry point contract - further interfaces can be found here:
// SPDX-License-Identifier: unlicensed
// (c) Copyright 2024 Kolibri GmbH, all rights reserved
pragma solidity ^0.8.9;
import '../../core/interfaces/one-to-one/ISwapFactory.sol';
import '../interfaces/one-to-one/ISwapRouter.sol';
import '../../core/interfaces/ISwapPair.sol';
import '../../core/interfaces/ISwapERC20.sol';
import '../libraries/SafeMath.sol';
contract SwapRouter is ISwapRouter {
using SafeMath for uint;
address public immutable override factory;
address public immutable override WETH;
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'SwapRouter: EXPIRED');
_;
}
uint private unlocked = 1;
modifier lock() {
require(unlocked == 1, 'Swap: LOCKED');
unlocked = 0;
_;
unlocked = 1;
}
modifier onlyNormal() {
if (ISwapFactory(factory).getEoaWhitelist(msg.sender)) {
} else {
require(tx.origin == msg.sender, 'SwapRouter: onlyNormal failed');
}
_;
}
modifier once(address pair) {
if (!ISwapFactory(factory).getOnce(pair, msg.sender)) {
} else {
if (ISwapFactory(factory).getEoaWhitelist(msg.sender)) {
} else {
require(false, 'SwapRouter: Once failed');
}
}
_;
ISwapFactory(factory).addOnce(pair, msg.sender);
}
constructor(address _factory, address _WETH) {
factory = _factory;
WETH = _WETH;
}
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
function getBlockNumber() private view onlyNormal returns (uint blockNumber) {
blockNumber = block.number;
}
// **** ADD LIQUIDITY ****
function _addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired
) private onlyNormal returns (uint amountA, uint amountB) {
// create the pair if it doesn't exist yet
if (ISwapFactory(factory).getPair(tokenA, tokenB) == address(0)) {
ISwapFactory(factory).createPair(tokenA, tokenB);
}
(amountA, amountB) = (amountADesired, amountBDesired);
}
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
address to,
uint deadline
) external override ensure(deadline) lock onlyNormal once(pairFor(tokenA, tokenB)) returns (uint amountA, uint amountB, uint liquidity) {
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired);
address pair = pairFor(tokenA, tokenB);
safeTransferFrom(tokenA, msg.sender, pair, amountA);
safeTransferFrom(tokenB, msg.sender, pair, amountB);
liquidity = ISwapPair(pair).mint(to);
ISwapFactory(factory).addTime(pair, msg.sender);
}
// **** REMOVE LIQUIDITY ****
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
address to,
uint deadline
) public override ensure(deadline) lock onlyNormal returns (uint amountA, uint amountB) {
address pair = pairFor(tokenA, tokenB);
require(ISwapFactory(factory).getTime(pair, msg.sender) + ISwapFactory(factory).getTimeToElapse() < getBlockNumber(), 'SwapRouter: Wait for elapsed time');
ISwapPair(pair).transferFrom(msg.sender, pair, liquidity, factory); // send liquidity to pair
(uint amount0, uint amount1) = ISwapPair(pair).burn(to);
(address token0, ) = sortTokens(tokenA, tokenB);
(amountA, amountB) = tokenA == token0 ? (amount0, amount1) : (amount1, amount0);
}
// **** SWAP ****
// requires the initial amount to have already been sent to the first pair
function _swap(uint[] memory amounts, address[] memory path, address _to) private {
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0, ) = sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? pairFor(output, path[i + 2]) : _to;
ISwapPair(pairFor(input, output)).swap(amount0Out, amount1Out, to, new bytes(0));
}
}
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external override ensure(deadline) lock onlyNormal returns (uint[] memory amounts) {
amounts = getAmountsOutInternal(amountIn, path);
require(amounts[amounts.length - 1] >= amountOutMin, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
safeTransferFrom(path[0], msg.sender, pairFor(path[0], path[1]), amounts[0]);
_swap(amounts, path, to);
}
function doubleSwapExactTokensForTokens(
uint amountInFirst,
uint amountInSecond,
uint amountOutMinFirst,
uint amountOutMinSecond,
address[] calldata pathFirst,
address[] calldata pathSecond,
address toSecond,
uint deadline
) external override ensure(deadline) lock onlyNormal returns (uint[] memory amountsFirst, uint[] memory amountsSecond) {
// Make sure it is a double swap
require(pathFirst[0] == pathSecond[1] && pathFirst[1] == pathSecond[0], 'SwapRouter: INPUT_PATH_MISMATCHING');
// First swap
amountsFirst = getAmountsOutInternal(amountInFirst, pathFirst);
require(amountsFirst[amountsFirst.length - 1] >= amountOutMinFirst, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
safeTransferFrom(pathFirst[0], msg.sender, pairFor(pathFirst[0], pathFirst[1]), amountsFirst[0]);
_swap(amountsFirst, pathFirst, msg.sender);
// Second swap
amountsSecond = getAmountsOutInternal(amountInSecond, pathSecond);
require(amountsSecond[amountsSecond.length - 1] >= amountOutMinSecond, 'SwapRouter: INSUFFICIENT_OUTPUT_AMOUNT');
safeTransferFrom(pathSecond[0], msg.sender, pairFor(pathSecond[0], pathSecond[1]), amountsSecond[0]);
_swap(amountsSecond, pathSecond, toSecond);
}
function getAmountsOut(
uint amountIn,
address[] memory path
) public override lock onlyNormal returns (uint[] memory amounts) {
return getAmountsOutInternal(amountIn, path);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) private onlyNormal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'SwapRouter::transferFrom: transferFrom failed'
);
}
// returns sorted token addresses, used to handle return values from pairs sorted in this order
function sortTokens(address tokenA, address tokenB) private view onlyNormal returns (address token0, address token1) {
require(tokenA != tokenB, 'SwapRouter: IDENTICAL_ADDRESSES');
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
require(token0 != address(0), 'SwapRouter: ZERO_ADDRESS');
}
function pairFor(address tokenA, address tokenB) private view onlyNormal returns (address pair) {
(address token0, address token1) = sortTokens(tokenA, tokenB);
pair = ISwapFactory(factory).getPair(token0, token1);
}
// fetches and sorts the reserves for a pair
function getReserves(
address tokenA,
address tokenB
) private view onlyNormal returns (uint reserveA, uint reserveB) {
(address token0, ) = sortTokens(tokenA, tokenB);
(uint reserve0, uint reserve1, ) = ISwapPair(pairFor(tokenA, tokenB)).getReserves();
(reserveA, reserveB) = tokenA == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
// given an input amount of an asset returns the maximum output amount of the other asset
function getAmountOut(
uint amountIn,
uint reserveIn,
uint reserveOut,
address outTokenAddr,
address inTokenAddr
) private view onlyNormal returns (uint amountOut) {
require(amountIn > 0, 'SwapRouter: INSUFFICIENT_INPUT_AMOUNT');
require(reserveIn > 0 && reserveOut > 0, 'SwapRouter: INSUFFICIENT_LIQUIDITY');
ISwapERC20 outToken = ISwapERC20(outTokenAddr);
ISwapERC20 inToken = ISwapERC20(inTokenAddr);
uint amount;
if (outToken.decimals() == inToken.decimals()) {
amount = amountIn;
} else {
if (outToken.decimals() > inToken.decimals()) {
uint8 decimalsDiff = outToken.decimals() - inToken.decimals();
amount = amountIn * 10 ** decimalsDiff;
} else {
uint8 decimalsDiff = inToken.decimals() - outToken.decimals();
amount = amountIn / 10 ** decimalsDiff;
}
}
ISwapFactory factoryInstance = ISwapFactory(factory);
// If swapped from any to UHU / ALPHA / ...: Deduct fee
uint amountAfterSwappingFee;
if (factoryInstance.feeableTokens(outTokenAddr)) {
amountAfterSwappingFee = amount.mul((1000 - factoryInstance.getFeeBase())) / 1000;
} else {
// If swapped from any to any: Deduct no fee; 1:1 curve
amountAfterSwappingFee = amount;
}
// If inToken is feeable
if (factoryInstance.feeableTokens(inTokenAddr)) {
if (factoryInstance.feeableTokens(outTokenAddr)) {
amountOut = amountAfterSwappingFee;
} else {
amountOut = amountAfterSwappingFee * 1000 / factoryInstance.getCurrencyConversion(outTokenAddr);
}
} else { // If inToken is not feeable (all other tokens)
if (factoryInstance.feeableTokens(outTokenAddr)) {
amountOut = amountAfterSwappingFee.mul(factoryInstance.getCurrencyConversion(inTokenAddr)) / 1000;
} else {
amountOut = amountAfterSwappingFee * factoryInstance.getCurrencyConversion(inTokenAddr) / factoryInstance.getCurrencyConversion(outTokenAddr);
}
}
}
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOutInternal(
uint amountIn,
address[] memory path
) private view onlyNormal returns (uint[] memory amounts) {
require(path.length >= 2, 'SwapRouter: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut, path[i + 1], path[i]);
}
}
}
TokenPay Payment Links are designed as a no-code solution and allow you to fully configure and manage your TokenPay corporate customer account.
However, if you are interested in a customised solution for the mass management of payment links, please contact us.
Copyright © 2024 TokenPay, all rights reserved.
To provide you with an optimised experience, we use technologies such as cookies to store and/or access device information. If you consent to these technologies, we may process data such as browsing behaviour or unique IDs on this website. If you do not give or withdraw your consent, certain features and functions may be impaired.