Contract Integration
This guide explains how to integrate your smart contracts with the OnchainPoints ecosystem, including both points spending and staking functionality.
Points Integration
There are two main ways to integrate points spending in your contract: direct spending and delegated spending.
Direct Spending Integration
When users need to spend their points directly through your contract, you have two options:
spendTokenWithoutSignature
This function allows you to spend tokens without requiring a signature. It's useful for cases where the user has funds for gas and are submitting the transaction themselves.
/**
* @dev Spends tokens without requiring a signature
* @param amount The amount of tokens to spend
*/
function spendTokenWithoutSignature(uint256 amount) external {}
spendToken
This function allows you to spend tokens based on a signed request. It's useful for cases where the user doesn't have funds for gas and you want to relay the transaction.
/**
* @dev Spends tokens based on a signed request
* @param request The spending request
* @param signature The signature of the request
* @return spender The address of the spender
*/
function spendToken(Request calldata request, bytes calldata signature) external returns(address spender) {}
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IOnchainPoints {
struct Request {
uint256 deadline;
string nonce;
uint256 amount;
}
function spendToken(Request calldata request, bytes calldata signature)
external
returns(address spender);
}
contract YourContract {
IOnchainPoints public onchainPoints;
constructor(address onchainPointsAddress) {
onchainPoints = IOnchainPoints(onchainPointsAddress);
}
function purchaseWithPoints(
uint256 amount,
Request calldata request,
bytes calldata signature
) external {
// Spend the points
onchainPoints.spendToken(request, signature);
// Your contract logic here
// e.g., mint NFTs, provide service, etc.
}
}
Delegated Spending Integration
Delegating tokens is simple, you just need to call approve
on the Onchain Points contract with the address and amount of tokens you want to delegate.
/**
* @dev Approves a spender to spend tokens on behalf of the owner
* @param spender The address of the spender
* @param amount The amount of tokens to approve
*/
function approve(address spender, uint256 amount) external {
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
}
spendTokensOnBehalf
This function allows users to spend tokens delegated to them. It's useful for cases where the user doesn't have funds for gas and you want to relay the transaction.
/**
* @dev Spends tokens on behalf of another user
* @param request The delegated spending request
* @param signature The signature of the request
*/
function spendTokensOnBehalf(DelegatedRequest calldata request, bytes calldata signature) external {}
spendTokensOnBehalfWithoutSignature
This function allows users to spend tokens delegated to them without requiring a signature. It's useful for cases where the user has funds for gas and he is submitting the transaction himself.
/**
* @dev Spends tokens on behalf of another user without requiring a signature
* @param amount The amount of tokens to spend
* @param owner The address of the token owner
*/
function spendTokensOnBehalfWithoutSignature(uint256 amount, address owner) external {}
Example
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IOnchainPoints {
struct DelegatedRequest {
uint256 deadline;
string nonce;
uint256 amount;
address owner;
}
function spendTokensOnBehalf(DelegatedRequest calldata request, bytes calldata signature) external;
function spendTokensOnBehalfWithoutSignature(uint256 amount, address owner) external;
}
contract YourDelegatedContract {
IOnchainPoints public onchainPoints;
constructor(address onchainPointsAddress) {
onchainPoints = IOnchainPoints(onchainPointsAddress);
}
// For cases where user has signed a delegated request
function purchaseWithDelegatedPoints(
uint256 amount,
DelegatedRequest calldata request,
bytes calldata signature
) external {
require(onchainPoints.allowances(request.owner, address(this)) >= amount, "Not enough delegated points");
// Spend the delegated points
onchainPoints.spendTokensOnBehalf(request, signature);
// Your contract logic here
// e.g., mint NFTs, provide service, etc.
}
// For cases where user has approved spending and has gas
function purchaseWithDelegatedPointsNoSignature(
uint256 amount,
address owner
) external {
require(onchainPoints.allowances(owner, address(this)) >= amount, "Not enough delegated points");
// Spend the delegated points without signature
onchainPoints.spendTokensOnBehalfWithoutSignature(amount, owner);
// Your contract logic here
// e.g., mint NFTs, provide service, etc.
}
}
Generating a Signed Request
If you want to fund user transactions or relay them, you will need to ask users to sign a request. It's an EIP-712 typed data with the following structure:
/**
* @dev Struct to represent a spending request
* @param deadline Timestamp after which the request is no longer valid
* @param nonce Unique identifier to prevent replay attacks
* @param amount The amount of tokens to spend
*/
struct Request {
uint256 deadline;
string nonce;
uint256 amount;
}
or for delegated spending:
/**
* @dev Struct to represent a delegated spending request
* @param deadline Timestamp after which the request is no longer valid
* @param nonce Unique identifier to prevent replay attacks
* @param amount The amount of tokens to spend
* @param owner The address of the token owner
*/
struct DelegatedRequest {
uint256 deadline;
string nonce;
uint256 amount;
address owner;
}
Here's how you can generate a signed request for spending tokens in JavaScript:
const domain = {
name: 'OnchainPointsContract',
version: '0.1',
chainId: 17071,
verifyingContract: '0x7Af8F0F2B8216631cb315D9C44b5893465A7D687',
};
const types = {Request:[
{name: 'deadline', type: 'uint256'},
{name: 'nonce', type: 'string'},
{name: 'amount', type: 'uint256'},
]};
const data = {
deadline: new Date().getTime() + 1000,
nonce: new Date().getTime().toString(),
amount: BigInt(amount * 10**18),
}
let signature;
try {
signature = await $signer.signTypedData(domain, types, data);
} catch (error) {
alert('Error signing transaction '+error.message);
console.error(error);
return;
}
And here's how you can generate a signed request for delegated spending:
const domain = {
name: 'OnchainPointsContract',
version: '0.1',
chainId: 17071,
verifyingContract: '0x7Af8F0F2B8216631cb315D9C44b5893465A7D687',
};
const types = {
DelegatedRequest: [
{name: 'deadline', type: 'uint256'},
{name: 'nonce', type: 'string'},
{name: 'amount', type: 'uint256'},
{name: 'owner', type: 'address'},
]
};
const data = {
deadline: new Date().getTime() + 1000,
nonce: new Date().getTime().toString(),
amount: BigInt(amount * 10**18),
owner: currentAddress, // The address of the connected wallet
};
console.log(domain, types, data);
let signature;
try {
// Always use delegatedWallet for signing
signature = await delegatedWallet.signTypedData(domain, types, data);
} catch (error) {
alert('Error signing transaction: ' + error.message);
console.error(error);
return;
}
Staking Integration
The OnchainPoints ecosystem includes staking functionality that allows users to earn points through staking. Here's how to integrate with the staking features:
Points from Staking
Users who stake in the staking contract automatically earn points that can be spent through the OnchainPoints contract. There are several functions to interact with staking points:
getPointsEarnedFromStaking
Returns the total points earned from staking for a user:
/**
* @dev Returns the total points earned from staking for a user
* @param user The address of the user
* @return uint256 The total points earned
*/
function getPointsEarnedFromStaking(address user) external view returns(uint256)
getRemainingPointsEarnedFromStaking
Returns the remaining unspent points earned from staking:
/**
* @dev Returns the remaining unspent points earned from staking
* @param user The address of the user
* @return uint256 The remaining points
*/
function getRemainingPointsEarnedFromStaking(address user) external view returns(uint256)
spendableStakingPoints
Returns the amount of staking points that can currently be spent:
/**
* @dev Returns the amount of staking points that can currently be spent
* @return uint256 The amount of staking points
*/
function spendableStakingPoints() external view returns(uint256)
Example Integration
Here's how to integrate with the staking points system:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
interface IOnchainPoints {
function getPointsEarnedFromStaking(address user) external view returns(uint256);
function getRemainingPointsEarnedFromStaking(address user) external view returns(uint256);
function spendableStakingPoints(address user) external view returns(uint256);
}
contract YourContract {
IOnchainPoints public onchainPoints;
constructor(address onchainPointsAddress) {
onchainPoints = IOnchainPoints(onchainPointsAddress);
}
function checkUserPoints(address user) external view returns (
uint256 totalPoints,
uint256 remainingPoints,
uint256 spendablePoints
) {
totalPoints = onchainPoints.getPointsEarnedFromStaking(user);
remainingPoints = onchainPoints.getRemainingPointsEarnedFromStaking(user);
spendablePoints = onchainPoints.spendableStakingPoints(user);
}
// Your contract can then use these points for spending
// using the spending functions described in the Points Integration section
}
When spending points, the contract will automatically use staking points first before using the user's regular point balance. This process is handled transparently by the spending functions described in the Points Integration section.