Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TASK-6263] feat: improve requests usability #157

Merged
merged 8 commits into from
Oct 17, 2024
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export ALCHEMY_API_KEY=""
export MORALIS_API_KEY=""

# Peanut API key
export PEANUT_API_URL="https://api.staging.peanut.to"
export PEANUT_DEV_API_KEY=""
export ETHERSCAN_API_KEY=""

4 changes: 3 additions & 1 deletion src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export interface IPrepareXchainRequestFulfillmentTransactionProps {
provider: ethers.providers.Provider
apiUrl?: string
tokenType: EPeanutLinkType
APIKey?: string
}

export interface ISubmitRequestLinkFulfillmentProps {
Expand Down Expand Up @@ -177,8 +178,9 @@ export async function prepareXchainRequestFulfillmentTransaction({
provider,
apiUrl = 'https://api.peanut.to/',
tokenType,
APIKey,
}: IPrepareXchainRequestFulfillmentTransactionProps): Promise<interfaces.IPrepareXchainRequestFulfillmentTransactionProps> {
const linkDetails = await getRequestLinkDetails({ link: link, apiUrl: apiUrl })
const linkDetails = await getRequestLinkDetails({ link, apiUrl, APIKey })
let { tokenAddress: destinationToken } = linkDetails
let {
chainId: destinationChainId,
Expand Down
356 changes: 149 additions & 207 deletions test/basic/RequestLinkXChain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,216 +6,158 @@ import { BigNumber } from 'ethersv5'
import { EPeanutLinkType } from '../../src/consts/interfaces.consts'
dotenv.config()

describe('Peanut XChain request links fulfillment tests', function () {
it('Create a request link and fulfill it cross-chain', async function () {
peanut.toggleVerbose(true)
const userPrivateKey = process.env.TEST_WALLET_X_CHAIN_USER!
// const relayerPrivateKey = process.env.TEST_WALLET_X_CHAIN_RELAYER!

// Parameters that affect the test behaviour
const sourceChainId = '10' // Optimism
const destinationChainId = '137' // Polygon
const amountToTestWith = 0.1
const tokenDecimals = 6
const APIKey = process.env.PEANUT_DEV_API_KEY!
const sourceChainProvider = await getDefaultProvider(sourceChainId)
console.log('Source chain provider', sourceChainProvider)

const userSourceChainWallet = new ethers.Wallet(userPrivateKey, sourceChainProvider)

const recipientAddress = '0x42A5DC31169Da17639468e7Ffa271e90Fdb5e85A'
const tokenAddress = '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' // USDC on Optimism
const destinationToken = '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' // USDT on Polygon
const initialBalance = await peanut.getTokenBalance({
tokenAddress: destinationToken,
walletAddress: recipientAddress,
chainId: destinationChainId,
})
const { link } = await peanut.createRequestLink({
chainId: destinationChainId,
tokenAddress: destinationToken,
tokenAmount: amountToTestWith.toString(),
tokenType: EPeanutLinkType.erc20,
tokenDecimals: tokenDecimals.toString(),
recipientAddress,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/withFormData',
})
console.log('Created a request link on the source chain!', link)

const linkDetails = await peanut.getRequestLinkDetails({
link,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
})
console.log('Got the link details!', linkDetails)

const xchainUnsignedTxs = await peanut.prepareXchainRequestFulfillmentTransaction({
fromChainId: sourceChainId,
senderAddress: userSourceChainWallet.address,
fromToken: tokenAddress,
link,
squidRouterUrl: getSquidRouterUrl(true, false),
provider: sourceChainProvider,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
fromTokenDecimals: 6,
tokenType: EPeanutLinkType.erc20,
})
console.log('Computed x chain unsigned fulfillment transactions', xchainUnsignedTxs)

for (const unsignedTx of xchainUnsignedTxs.unsignedTxs) {
const { tx, txHash } = await signAndSubmitTx({
unsignedTx,
structSigner: {
signer: userSourceChainWallet,
gasLimit: BigNumber.from(2_000_000),
},
})

console.log('Submitted a transaction to fulfill the request link with tx hash', txHash)
await tx.wait()
console.log('Request link fulfillment initiated!')
const retry = async (assertion: () => Promise<void>, { times = 3, interval = 100 } = {}) => {
for (let i = 0; i < times; i++) {
try {
await assertion()
return
} catch (err) {
if (i === times - 1) throw err
await new Promise((resolve) => setTimeout(resolve, interval))
}
}
}

const finalBalance = await peanut.getTokenBalance({
tokenAddress: destinationToken,
walletAddress: recipientAddress,
chainId: destinationChainId,
})
console.log('Final balance of recipient:', finalBalance)
// expect(finalBalance).toBe((Number(initialBalance) + amountToTestWith).toString())
}, 120000)

it('Create a request link and fulfill it cross-chain native token', async function () {
peanut.toggleVerbose(true)
const userPrivateKey = process.env.TEST_WALLET_X_CHAIN_USER!

// Parameters that affect the test behaviour
const sourceChainId = '10' // Optimism
const destinationChainId = '42161' // Arbitrum
const amountToTestWith = 0.0001
const tokenDecimals = '18'
const APIKey = process.env.PEANUT_DEV_API_KEY!
const sourceChainProvider = await getDefaultProvider(sourceChainId)
console.log('Source chain provider', sourceChainProvider)

const userSourceChainWallet = new ethers.Wallet(userPrivateKey, sourceChainProvider)

const recipientAddress = '0x42A5DC31169Da17639468e7Ffa271e90Fdb5e85A'
const tokenAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // ETH on Optimism
const destinationToken = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // ETH on Arbitrum

const { link } = await peanut.createRequestLink({
chainId: destinationChainId,
tokenAddress: destinationToken,
tokenAmount: amountToTestWith.toString(),
tokenType: EPeanutLinkType.native,
tokenDecimals,
recipientAddress,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/withFormData',
})
console.log('Created a request link on the source chain!', link)

const linkDetails = await peanut.getRequestLinkDetails({
link,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
})
console.log('Got the link details!', linkDetails)

const xchainUnsignedTxs = await peanut.prepareXchainRequestFulfillmentTransaction({
fromChainId: sourceChainId,
senderAddress: userSourceChainWallet.address,
fromToken: tokenAddress,
link,
squidRouterUrl: getSquidRouterUrl(true, false),
provider: sourceChainProvider,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
tokenType: EPeanutLinkType.native,
fromTokenDecimals: 18,
})
console.log('Computed x chain unsigned fulfillment transactions', xchainUnsignedTxs)

for (const unsignedTx of xchainUnsignedTxs.unsignedTxs) {
const { tx, txHash } = await signAndSubmitTx({
unsignedTx,
structSigner: {
signer: userSourceChainWallet,
gasLimit: BigNumber.from(2_000_000),
},
describe('Peanut XChain request links fulfillment tests', function () {
it.each([
{
amount: '0.1',
sourceToken: {
chain: '10',
address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism
decimals: 6,
name: 'USDC on Optimism',
type: EPeanutLinkType.erc20,
},
destinationToken: {
chain: '137',
address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', // USDT on Polygon
decimals: 6,
name: 'USDT on Polygon',
type: EPeanutLinkType.erc20,
},
},
{
amount: '0.0001',
sourceToken: {
chain: '10',
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH on Optimism
decimals: 18,
name: 'ETH on Optimism',
type: EPeanutLinkType.native,
},
destinationToken: {
chain: '42161',
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH on Arbitrum
decimals: 18,
name: 'ETH on Arbitrum',
type: EPeanutLinkType.native,
},
},
{
amount: '0.1',
sourceToken: {
chain: '10',
address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', // ETH on Optimism
decimals: 18,
name: 'ETH on Optimism',
type: EPeanutLinkType.native,
},
destinationToken: {
chain: '10',
address: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85', // USDC on Optimism
decimals: 6,
name: 'USDC on Optimism',
type: EPeanutLinkType.erc20,
},
},
])(
'$sourceToken.name to $destinationToken.name',
async ({ amount, sourceToken, destinationToken }) => {
peanut.toggleVerbose(true)
const userPrivateKey = process.env.TEST_WALLET_PRIVATE_KEY!

// Parameters that affect the test behaviour
const apiUrl = process.env.PEANUT_API_URL!
const APIKey = process.env.PEANUT_DEV_API_KEY!
const sourceChainProvider = await getDefaultProvider(sourceToken.chain)
console.log('Source chain provider', sourceChainProvider)

const userSourceChainWallet = new ethers.Wallet(userPrivateKey, sourceChainProvider)

const recipientAddress = new ethers.Wallet(process.env.TEST_WALLET_PRIVATE_KEY2!).address
const initialBalance = await peanut.getTokenBalance({
tokenAddress: destinationToken.address,
walletAddress: recipientAddress,
chainId: destinationToken.chain,
})

console.log('Submitted a transaction to fulfill the request link with tx hash', txHash)
await tx.wait()
console.log('Request link fulfillment initiated!')
}
}, 120000)

it('Create a request link and fulfill it cross-chain native token', async function () {
peanut.toggleVerbose(true)
const userPrivateKey = process.env.TEST_WALLET_X_CHAIN_USER!
// const relayerPrivateKey = process.env.TEST_WALLET_X_CHAIN_RELAYER!

// Parameters that affect the test behaviour
const sourceChainId = '10' // Arbitrum
const destinationChainId = '10' // Optimism
const amountToTestWith = 0.1
// const tokenDecimals = '18'
const APIKey = process.env.PEANUT_DEV_API_KEY!
const sourceChainProvider = await getDefaultProvider(sourceChainId)
console.log('Source chain provider', sourceChainProvider)

const userSourceChainWallet = new ethers.Wallet(userPrivateKey, sourceChainProvider)

const recipientAddress = '0x42A5DC31169Da17639468e7Ffa271e90Fdb5e85A'
const tokenAddress = '0x0000000000000000000000000000000000000000' // ETH on Optimism
const destinationToken = '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' // USDC on Optimism

const { link } = await peanut.createRequestLink({
chainId: destinationChainId,
tokenAddress: destinationToken,
tokenAmount: amountToTestWith.toString(),
tokenType: EPeanutLinkType.erc20,
tokenDecimals: '6',
recipientAddress,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/withFormData',
})
console.log('Created a request link on the source chain!', link)

const linkDetails = await peanut.getRequestLinkDetails({
link,
APIKey,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
})
console.log('Got the link details!', linkDetails)

const xchainUnsignedTxs = await peanut.prepareXchainRequestFulfillmentTransaction({
fromChainId: sourceChainId,
senderAddress: userSourceChainWallet.address,
fromToken: tokenAddress,
link,
squidRouterUrl: getSquidRouterUrl(true, false),
provider: sourceChainProvider,
apiUrl: 'https://staging.peanut.to/api/proxy/get',
tokenType: EPeanutLinkType.native,
fromTokenDecimals: 18,
})
console.log('Computed x chain unsigned fulfillment transactions', xchainUnsignedTxs)

for (const unsignedTx of xchainUnsignedTxs.unsignedTxs) {
const { tx, txHash } = await signAndSubmitTx({
unsignedTx,
structSigner: {
signer: userSourceChainWallet,
gasLimit: BigNumber.from(2_000_000),
},
console.log('Initial balance of recipient:', initialBalance)

const { link } = await peanut.createRequestLink({
chainId: destinationToken.chain,
tokenAddress: destinationToken.address,
tokenAmount: amount,
tokenType: destinationToken.type,
tokenDecimals: destinationToken.decimals.toString(),
recipientAddress,
APIKey,
apiUrl,
})
console.log('Created a request link on the source chain!', link)

console.log('Submitted a transaction to fulfill the request link with tx hash', txHash)
await tx.wait()
console.log('Request link fulfillment initiated!')
}
}, 120000)
const linkDetails = await peanut.getRequestLinkDetails({
link,
APIKey,
apiUrl,
})
console.log('Got the link details!', linkDetails)

const xchainUnsignedTxs = await peanut.prepareXchainRequestFulfillmentTransaction({
fromChainId: sourceToken.chain,
senderAddress: userSourceChainWallet.address,
fromToken: sourceToken.address,
link,
squidRouterUrl: getSquidRouterUrl(true, false),
provider: sourceChainProvider,
tokenType: sourceToken.type,
fromTokenDecimals: sourceToken.decimals,
apiUrl,
APIKey,
})
console.log('Computed x chain unsigned fulfillment transactions', xchainUnsignedTxs)

for (const unsignedTx of xchainUnsignedTxs.unsignedTxs) {
const { tx, txHash } = await signAndSubmitTx({
unsignedTx,
structSigner: {
signer: userSourceChainWallet,
gasLimit: BigNumber.from(2_000_000),
},
})

console.log('Submitted a transaction to fulfill the request link with tx hash', txHash)
await tx.wait()
console.log('Request link fulfillment initiated!')
}
// how many digits to check for equality after the decimal point
const numDigits = Math.floor(Math.log10(1 / Number(amount))) + 1
const expectedBalance = Number(initialBalance) + Number(amount)

await retry(
async () => {
const finalBalance = await peanut.getTokenBalance({
tokenAddress: destinationToken.address,
walletAddress: recipientAddress,
chainId: destinationToken.chain,
})
console.log(
`Final balance of recipient: ${finalBalance}, expected: ${expectedBalance}, with tolerance: ${numDigits}`
)
expect(Number(finalBalance)).toBeCloseTo(expectedBalance, numDigits)
},
{ times: 15, interval: 2000 }
) // retry for up to 30 seconds
},
120000
)
})