Enable your users to deposit into syrupUSDC & syrupUSDT through your own app. Manage deposits programmatically. For wallets, exchanges, custody solutions etc.
Within the SDK, both ABIs and network-specific addresses are accessible. The package can also be installed and used within applications.
npm install @maplelabs/maple-js
yarn add @maplelabs/maple-js
pnpm add @maplelabs/maple-js
To access the necessary data, use the GraphQL API:
https://api.maple.finance/v2/graphql
https://sepolia.api.maple.finance/v2/graphql
NOTE: In order to perform the integration in Sepolia, you'll need to contact us at [email protected] to receive Sepolia USDC/USDT tokens.
Deposit
1. Query the Maple API
Syrup's main contract, the SyrupRouter, is designed to allow authorized participants to securely access the yields available in the Maple ecosystem, abstracting all the complexity of the permissioning system. Each Syrup PoolV2 has an associated SyrupRouter.
If isSyrupLender = true, the user is already authorized in the Pool Permission Manager and can deposit into Syrup pools. Otherwise, the user must perform authorization before their initial deposit.
3. Retrieve Authorization Signature
Note: This step is only required if isSyrupLender = false was returned in the previous step. Otherwise, continue to the next step.
The Maple Protocol is geared towards institutions and has a permissioning system that requires allowlisting for executing most functions. For pool deposits, in general, lenders need to have their wallet allowlisted in Maple's Pool Permission Manager. Aiming to abstract and simplify the process, the SyrupRouter integrates directly with the Pool Permission Manager to allow for valid users to self-authorize and deposit in a single transaction assuming the user meets eligibility requirements.
To retrieve the authorization signature, contact us at [email protected].
Deposit data
Replace 0:<integrator-name> with your integrator identifier (e.g. 0:acme-protocol).
Maple will provide the final depositData for production.
Must be passed as bytes32 (32-byte hex).
4. Execute the Deposit
The first time an asset (e.g., USDC, USDT) is lent to Syrup, it may be necessary to allow the contract to interact with the asset. This is a common transaction on Ethereum.
Depositing into a pool requires a transaction. Once the transaction has been processed, SyrupRouter will accept the lending tokens and Pool LP (Liquidity Provider) tokens will be received.
Each pool contract inherits the ERC-4626 standard, also known as the Tokenized Vault Standard. This standard informs how the LP Tokens accrue value from borrower repayments of loans.
isSyrupLender = true - User is Already Authorized
The deposit or depositWithPermit method can be called directly on SyrupRouter.
function deposit(uint256 assets, bytes32 depositData)
NOTE: depositData is an optional field that does not need to be provided.
deposit()
Code example using Maple SDK for USDC or USDT.
import { BigNumber, Contract, providers, utils, Wallet } from 'ethers';
import { addresses, syrupUtils } from '@maplelabs/maple-js';
const main = async () => {
const provider = new providers.JsonRpcProvider(RPC_URL);
const signer = new Wallet(PRIVATE_KEY, provider);
const syrupRouter = syrupUtils.syrupRouter.connect(
SYRUP_USDC_ROUTER_ADDRESS, // Explained how to get in previous step
signer
);
const usdc = new Contract(addresses['mainnet-prod'].USDC, USDC_ABI, signer);
const amount = BigNumber.from(1000000);
const approveReceipt = await usdc.approve(SYRUP_USDC_ROUTER_ADDRESS, amount);
await approveReceipt.wait();
const depositReceipt = await syrupRouter.deposit(amount, utils.formatBytes32String('0:<integrator-name>'));
await depositReceipt.wait();
};
main();
import { BigNumber, Contract, providers, utils, Wallet } from 'ethers';
import { addresses, syrupUtils, environmentMocks } from '@maplelabs/maple-js';
const main = async () => {
const provider = new providers.JsonRpcProvider(RPC_URL);
const signer = new Wallet(PRIVATE_KEY, provider);
const syrupRouter = syrupUtils.syrupRouter.connect(
SYRUP_USDT_ROUTER_ADDRESS, // Explained how to get in previous step
signer
);
const usdt = new Contract(addresses['mainnet-prod'].USDT, USDT_ABI, signer);
const amount = BigNumber.from(1000000);
const approveReceipt = await usdt.approve(SYRUP_USDT_ROUTER_ADDRESS, amount);
await approveReceipt.wait();
const depositReceipt = await syrupRouter.deposit(amount, utils.formatBytes32String('0:<integrator-name>'));
await depositReceipt.wait();
};
main();
Deposits into Syrup can also be made with gasless approval using permit. For more information, see https://eips.ethereum.org/EIPS/eip-2612.
NOTE: authorizeAndDepositWithPermit is only available for Syrup USDC.
authAndDepositWithPermit()
Code example using Maple SDK
import { BigNumber, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";
const main = async () {
... // Exact same steps as regular depositWithPermit
const { bitmap, authDeadline, authSig } = authorize(); // Contact the Syrup team for authorization information
const authorizeAndDepositWithPermitReceipt = await syrupRouter.authorizeAndDepositWithPermit(
bitmap,
authDeadline,
authSig.v,
authSig.r,
authSig.s,
amount,
utils.formatBytes32String('0:<integrator-name>'),
deadline,
sig.v,
sig.r,
sig.s
);
await authorizeAndDepositWithPermitReceipt.wait();
}
main();
Withdraw
1. Retrieve Pool Position
Query the Maple API for the user's pool position data using the PoolV2Position field in the Account query:
Data model
PoolPositionV2 {
availableBalance // The pool position in the pool asset.
availableShares // The underlying shares representing that position.
redeemRequested // A boolean indicating if a redeem request is active.
}
Example query
query GetMapleAccount($accountId: ID!, $poolId: String!) {
account(id: $accountId) {
id
poolV2Positions(where: { pool: $poolId }) {
id // "$lender_address-$pool_address"
availableBalance // Position in pool asset
availableShares // Underlying shares
redeemRequested // Boolean flag
pool {
name
}
}
}
}
import { gql, GraphQLClient } from 'graphql-request';
interface MaplePoolPosition {
id: string;
availableBalance: string;
availableShares: string;
redeemRequested: boolean;
pool: {
name: string;
};
}
interface MapleAccount {
id: string;
poolV2Positions: MaplePoolPosition[];
}
interface QueryResponse {
account: MapleAccount;
}
const query = gql`
query GetMapleAccount($accountId: ID!, $poolId: String!) {
account(id: $accountId) {
id
poolV2Positions(where: { pool: $poolId }) {
id
availableBalance
availableShares
redeemRequested
pool {
name
}
}
}
}
`;
const client = new GraphQLClient(MAPLE_API_URL);
const main = async () => {
const account = '0x123...';
const { account: mapleAccount } = await client.request<QueryResponse>(query, {
accountId: account.toLowerCase(),
poolId: SYRUP_USDC,
});
if (mapleAccount) {
for (const poolV2Position of mapleAccount.poolV2Positions) {
console.log(`Pool ${poolV2Position.pool.name} position for ${account}`);
console.log(`Available balance: ${poolV2Position.availableBalance}`);
console.log(`Available shares: ${poolV2Position.availableShares}`);
}
} else {
console.log('Account not found');
}
};
main();
2. Calculate Shares to Redeem
Withdrawal requests must be expressed in shares. Although the Maple API provides both availableShares and availableBalance, losses or impairments on the pool may affect the value of assets relative to shares.
To ensure accuracy, convert the desired asset amount to "exit shares" using the pool contract's conversion method.
These transactions leverage the ERC-4626 tokenized vault standard. For more information, see https://ethereum.org/en/developers/docs/standards/tokens/erc-4626/.
Function signature
function convertToExitShares(uint256 assets_) external view returns (uint256 shares_);
Code example using Maple SDK
import { BigNumber, providers } from 'ethers';
import { poolV2 } from '@maplelabs/maple-js';
const main = async () => {
const provider = new providers.JsonRpcProvider(RPC_URL);
const syrupPool = poolV2.core.connect(SYRUP_USDC, provider);
const amount = BigNumber.from(10 ** 8);
const sharesToRedeem = await syrupPool.convertToExitShares(amount);
console.log(`${amount.toString()} is equal to ${sharesToRedeem.toString()}`);
};
main();
3. Execute the Withdrawal
After calculating sharesToRedeem or fetching availableShares, call the requestRedeem method on the Pool contract to initiate the withdrawal.
Function signature
function requestRedeem(
uint256 shares, // Shares to redeem (from Step 1 or Step 2)
address receiver // Address to receive the assets
)
Withdrawals are processed automatically by Maple. If there is sufficient liquidity in the pool, the withdrawal will be processed within a few minutes. Expected processing time is typically less than 2 days, but it can take up to 30 days depending on available liquidity. The current status of the withdrawal queue can be retrieved either directly from the WithdrawalManagerQueue contract or through the Maple GraphQL API:
Example query
query GetPoolV2Queue($id: ID!) {
poolV2(id: $id) {
withdrawalManagerQueue {
totalShares
nextRequest {
id
shares
status
}
}
}
}
When attempting to use deposit or depositWithPermit and receiving the error SR:D:NOT_AUTHORIZED, it indicates that the account being used for deposit is not authorized in Syrup USD. To authorize the account, use authorizeAndDeposit or authorizeAndDepositWithPermit.
import { BigNumber, providers, utils, Wallet } from "ethers";
import { addresses, syrupUtils } from "@maplelabs/maple-js";
const main = async () => {
...
const { bitmap, deadline, sig } = authorize(); // Contact the Syrup team for authorization information
const authorizeAndDepositReceipt = await syrupRouter.authorizeAndDeposit(
bitmap,
deadline,
sig.v,
sig.r,
sig.s,
amount,
utils.formatBytes32String('0:<integrator-name>')
);
await authorizeAndDepositReceipt.wait();
};
main();
Insufficient Approval
When attempting to use deposit or depositWithPermit and receiving the error SR:D:TRANSFER_FROM_FAIL, it indicates that SyrupRouter could not transfer lending tokens. Ensure that the deposit amount matches the current approval for SyrupRouter. If it does not, increase the approval.
When attempting to use depositWithPermit and receiving the error ERC20:P:INVALID_SIGNATURE, it indicates that the recovered address from the signature does not match the address attempting to deposit. Ensure that the signature is signed by the account executing the transaction.
Expired Signature
When attempting to use depositWithPermit and receiving the error ERC20:P:EXPIRED, it indicates that the permit signature has expired. Ensure that the deadline set on the signature provides sufficient time to execute the transaction.
Insufficient Gas
When transactions fail due to insufficient gas, increase the gas limit in the transaction parameters. Syrup operations require more gas than simple token transfers due to the complex authorization and routing logic.
When encountering error P:DEPOSIT_GT_LIQ_CAP, it means the deposit amount exceeds the pool's deposit limit. Check the pool's current capacity before attempting large deposits.
To perform a deposit larger than the limit, contact the Syrup team via Telegram.
FAQ
What is the difference between Syrup and Maple?
Syrup is the protocol - the smart contracts that power onchain lending. Maple encompasses the entities that conduct institutional lending services using this infrastructure.
For developers: You'll be integrating with Syrup (the protocol), while Maple handles the institutional lending operations.
What is the authorize and deposit function with permit?
The authorizeAndDepositWithPermit function combines three operations into a single transaction:
Authorization: Grants permission to deposit into Syrup pools (required for first-time users)
Permit: Provides gasless token approval using EIP-2612 standard
Deposit: Transfers tokens and mints pool shares
This function is ideal for first-time users who want to deposit USDC without requiring separate authorization and approval transactions. It saves gas and improves user experience by reducing the number of required transactions from three to one.
NOTE: Only available for USDC deposits, not USDT.
What are the permit signature parameters?
There are 5 parameters required for permit signature:
owner: The wallet address that owns the tokens and is granting permission (typically the user's address)
spender: The contract address receiving approval (the SyrupRouter contract address)
value: The token amount in smallest units (for USDC: 6 decimals, e.g., 1000000 = 1 USDC) that the spender is allowed to spend
nonce: A unique number preventing replay attacks (obtained by calling nonces(address) on the token contract). It increments with each permit signature used
Do I need authorization for smart contract integration?
Yes, authorization is required for all Syrup deposits. Syrup protocol is built by Maple, which uses a permissioning system for institutional-grade security.
Use authorizeAndDeposit or authorizeAndDepositWithPermit for first deposit
Subsequent deposits only need deposit or depositWithPermit
Once authorized, the permission persists across all Syrup pools and future deposits.
How do withdrawals work?
Withdrawals follow a queue-based system:
Request: Call requestRedeem() to enter the withdrawal queue
Queue Position: Withdrawals are processed first-in, first-out (FIFO)
Processing: When pool liquidity is available, withdrawals are automatically processed
Completion: Assets are sent directly to the wallet (no additional transaction required)
Timeline
Expected processing time is typically less than 2 days
During low liquidity periods, it may take up to 30 days
No penalties for withdrawing, but yield stops accumulating once withdrawal is requested
How long do withdrawals take?
syrupUSDC & syrupUSDT normally have instant liquidity, but in rare cases withdrawals can take around 24h with the maximum possible time being 30 days. You can see the available funds to withdraw in the Liquidity section of the Details page.
How can I get the APY data for syrupUSDC or syrupUSDT?
Querying the GraphQL API is the simplest way to get APY data for syrupUSDC or syrupUSDT into your app.