Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/react/src/components/Connected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const checkoutProvider = searchParams.get('checkoutProvider')
const onRampProvider = searchParams.get('onRampProvider')
const checkoutPreset = searchParams.get('checkoutPreset') || 'forte-payment-erc1155-sale-native-token-testnet'

// @ts-ignore
const isDev = __SEQUENCE_WEB_SDK_IS_DEV__

export const Connected = () => {
const [isOpenCustomCheckout, setIsOpenCustomCheckout] = React.useState(false)
const { setOpenConnectModal } = useOpenConnectModal()
Expand Down Expand Up @@ -419,9 +422,6 @@ export const Connected = () => {
recipientAddress: address,
creditCardProviders: [creditCardProvider],
onRampProvider: onRampProvider ? (onRampProvider as TransactionOnRampProvider) : TransactionOnRampProvider.transak,
transakConfig: {
contractId: '674eb5613d739107bbd18ed2'
},
onSuccess: (txnHash?: string) => {
console.log('success!', txnHash)
},
Expand Down
4 changes: 0 additions & 4 deletions examples/react/src/components/CustomCheckout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,6 @@ export const CustomCheckout = () => {
currencyAddress,
collectionAddress,
creditCardProvider: 'transak' as CreditCardProviders,
transakConfig: {
contractId,
apiKey: '5911d9ec-46b5-48fa-a755-d59a715ff0cf'
},
onSuccess: (txnHash?: string) => {
console.log('success!', txnHash)
},
Expand Down
2 changes: 0 additions & 2 deletions examples/react/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,6 @@ export const checkoutConfig: SequenceCheckoutConfig = {
? {
sardineCheckoutUrl: 'https://sardine-checkout-sandbox.sequence.info',
sardineOnRampUrl: 'https://crypto.sandbox.sardine.ai/',
transakApiUrl: 'https://global-stg.transak.com',
transakApiKey: 'c20f2a0e-fe6a-4133-8fa7-77e9f84edf98',
forteWidgetUrl: 'https://payments.sandbox.lemmax.com/forte-payments-widget.js'
}
: undefined
Expand Down
2 changes: 1 addition & 1 deletion examples/react/src/utils/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export const checkoutPresets: Record<string, (recipientAddress: string) => Check
}
}
},
'forte-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => {
'forte-transak-payment-erc1155-sale-native-token-testnet': (recipientAddress: string) => {
const collectibles = [
{
tokenId: '1',
Expand Down
4 changes: 0 additions & 4 deletions packages/checkout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,6 @@ const CustomCheckoutUI = () => {
currencyAddress,
collectionAddress,
creditCardProvider: 'transak' as CreditCardProviders,
transakConfig: {
contractId,
apiKey: '5911d9ec-46b5-48fa-a755-d59a715ff0cf'
},
onSuccess: (txnHash?: string) => {
console.log('success!', txnHash)
},
Expand Down
61 changes: 61 additions & 0 deletions packages/checkout/src/api/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,3 +499,64 @@ export const fetchFortePaymentStatus = async (
status: (statuses[0]?.status as FortePaymentStatus) || ''
}
}

export interface TransakNFTData {
imageURL: string
nftName: string
collectionAddress: string
tokenID: string[]
price: number[]
quantity: number
nftType: string
}

export interface TransakWidgetUrlArgs {
isNFT?: boolean
calldata?: string
targetContractAddress?: string
cryptoCurrencyCode?: string
estimatedGasLimit?: number
nftData?: TransakNFTData[]
walletAddress: string
disableWalletAddressForm?: boolean
partnerOrderId?: string
network?: string
referrerDomain: string
fiatAmount?: number
fiatCurrency?: string
defaultFiatAmount?: number
defaultCryptoCurrency?: string
cryptoCurrencyList?: string
networks?: string
}

export const getTransakWidgetUrl = async (
sequenceApiUrl: string,
projectAccessKey: string,
args: TransakWidgetUrlArgs
): Promise<{ url: string }> => {
const queryUrl = `${sequenceApiUrl}/rpc/API/TransakGetWidgetURL`

const res = await fetch(queryUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Access-Key': projectAccessKey
},
body: JSON.stringify({
params: {
...args
}
})
})

if (!res.ok) {
throw new Error(`Transak API error: ${res.status} ${res.statusText}`)
}

const { url } = await res.json()

return {
url
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,6 @@ export const SequenceCheckoutProvider = ({ children, config }: SequenceCheckoutP
marketplaceApiUrl: config?.env?.marketplaceApiUrl ?? 'https://marketplace-api.sequence.app',
sardineCheckoutUrl: config?.env?.sardineCheckoutUrl ?? 'https://sardine-checkout.sequence.info',
sardineOnRampUrl: config?.env?.sardineOnRampUrl ?? 'https://crypto.sardine.ai/',
transakApiUrl: config?.env?.transakApiUrl ?? 'https://global.transak.com',
transakApiKey: config?.env?.transakApiKey ?? '5911d9ec-46b5-48fa-a755-d59a715ff0cf',
forteWidgetUrl: config?.env?.forteWidgetUrl ?? 'https://payments.prod.lemmax.com/forte-payments-widget.js'
}}
>
Expand Down
2 changes: 0 additions & 2 deletions packages/checkout/src/contexts/CheckoutModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ interface OrderSummaryItem {
}

export interface TransakConfig {
apiKey?: string
contractId: string
callDataOverride?: string
}

Expand Down
2 changes: 0 additions & 2 deletions packages/checkout/src/contexts/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { createGenericContext } from './genericContext.js'

export interface EnvironmentOverrides {
marketplaceApiUrl: string
transakApiUrl: string
transakApiKey: string
sardineCheckoutUrl: string
sardineOnRampUrl: string
forteWidgetUrl: string
Expand Down
115 changes: 69 additions & 46 deletions packages/checkout/src/hooks/useCheckoutUI/useCreditCardPayment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import { compareAddress } from '@0xsequence/connect'
import { useConfig } from '@0xsequence/hooks'
import type { ContractInfo, TokenMetadata } from '@0xsequence/metadata'
import { findSupportedNetwork } from '@0xsequence/network'
import pako from 'pako'
import React, { useEffect, useRef } from 'react'
import React, { useEffect, useMemo, useRef } from 'react'
import { formatUnits, zeroAddress, type Hex } from 'viem'

import { fetchSardineOrderStatus } from '../../api/data.js'
import type { TransakConfig } from '../../contexts/CheckoutModal.js'
import { useEnvironmentContext } from '../../contexts/Environment.js'
import type { Collectible, CreditCardProviders } from '../../contexts/SelectPaymentModal.js'
import { TRANSAK_PROXY_ADDRESS } from '../../utils/transak.js'
import { TRANSAK_PROXY_ADDRESS, getCurrencyCode } from '../../utils/transak.js'
import { useSardineClientToken } from '../useSardineClientToken.js'

import { useTransakWidgetUrl } from '../useTransakWidgetUrl.js'
const POLLING_TIME = 10 * 1000
const TRANSAK_IFRAME_ID = 'credit-card-payment-transak-iframe'
const SARDINE_IFRAME_ID = 'credit-card-payment-sardine-iframe'
Expand Down Expand Up @@ -82,7 +82,11 @@ export const useCreditCardPayment = ({
const { env } = useConfig()
const disableSardineClientTokenFetch =
isLoadingTokenMetadatas || isLoadingCurrencyInfo || isLoadingCollectionInfo || creditCardProvider !== 'sardine'
const { transakApiUrl, sardineCheckoutUrl: sardineProxyUrl, transakApiKey: transakGlobalApiKey } = useEnvironmentContext()

const disableTransakWidgetUrlFetch =
isLoadingTokenMetadatas || isLoadingCurrencyInfo || isLoadingCollectionInfo || creditCardProvider !== 'transak'

const { sardineCheckoutUrl: sardineProxyUrl } = useEnvironmentContext()
const network = findSupportedNetwork(chain)
const error = errorCollectionInfo || errorTokenMetadata || errorCurrencyInfo
const isLoading = isLoadingCollectionInfo || isLoadingTokenMetadatas || isLoadingCurrencyInfo
Expand Down Expand Up @@ -119,11 +123,66 @@ export const useCreditCardPayment = ({
disableSardineClientTokenFetch
)

// Transak requires the recipient address to be the proxy address
// so we need to replace the recipient address with the proxy address in the calldata
// this is a weird hack so that credit card integrations are as simple as possible and should work 99% of the time
// If an issue arises, the user can override the calldata in the transak settings

const calldataWithProxy =
transakConfig?.callDataOverride ??
txData.replace(recipientAddress.toLowerCase().substring(2), TRANSAK_PROXY_ADDRESS.toLowerCase().substring(2))

const price = Number(formatUnits(BigInt(totalPriceRaw), Number(currencyDecimals || 18)))

const transakNftData = [
{
imageURL: tokenMetadata?.image || '',
nftName: tokenMetadata?.name || 'collectible',
collectionAddress: collectionAddress,
tokenID: [collectible.tokenId || ''],
price: [price],
quantity: Number(collectible.quantity),
nftType: dataCollectionInfo?.type || 'ERC721'
}
]

const estimatedGasLimit = 500000

const partnerOrderId = useMemo(() => {
return `${recipientAddress}-${new Date().getTime()}`
}, [recipientAddress])

// Note: the network name might not always line up with Transak. A conversion function might be necessary
const networkName = network?.name.toLowerCase()

const {
data: transakLinkData,
isLoading: isLoadingTransakLink,
error: errorTransakLink
} = useTransakWidgetUrl(
{
isNFT: true,
calldata: calldataWithProxy,
targetContractAddress,
cryptoCurrencyCode: getCurrencyCode({
chainId: network?.chainId || 137,
currencyAddress,
defaultCurrencyCode: currencySymbol || 'ETH'
}),
estimatedGasLimit,
nftData: transakNftData,
walletAddress: recipientAddress,
disableWalletAddressForm: true,
partnerOrderId,
network: networkName,
referrerDomain: window.location.origin
},
disableTransakWidgetUrlFetch
)

const missingCreditCardProvider = !creditCardProvider
const missingTransakConfig = !transakConfig && creditCardProvider === 'transak'
const transakApiKey = transakConfig?.apiKey || transakGlobalApiKey

if (missingCreditCardProvider || missingTransakConfig) {
if (missingCreditCardProvider) {
return {
error: new Error('Missing credit card provider or transak config'),
data: {
Expand All @@ -148,46 +207,10 @@ export const useCreditCardPayment = ({
}

if (creditCardProvider === 'transak') {
// Transak requires the recipient address to be the proxy address
// so we need to replace the recipient address with the proxy address in the calldata
// this is a weird hack so that credit card integrations are as simple as possible and should work 99% of the time
// If an issue arises, the user can override the calldata in the transak settings

const calldataWithProxy =
transakConfig?.callDataOverride ??
txData.replace(recipientAddress.toLowerCase().substring(2), TRANSAK_PROXY_ADDRESS.toLowerCase().substring(2))

const pakoData = Array.from(pako.deflate(calldataWithProxy))

const transakCallData = encodeURIComponent(btoa(String.fromCharCode.apply(null, pakoData)))

const price = Number(formatUnits(BigInt(totalPriceRaw), Number(currencyDecimals || 18)))

const transakNftDataJson = JSON.stringify([
{
imageURL: tokenMetadata?.image || '',
nftName: tokenMetadata?.name || 'collectible',
collectionAddress: collectionAddress,
tokenID: [collectible.tokenId],
price: [price],
quantity: Number(collectible.quantity),
nftType: dataCollectionInfo?.type || 'ERC721'
}
])

const transakNftData = encodeURIComponent(btoa(transakNftDataJson))

const estimatedGasLimit = '500000'

const partnerOrderId = `${recipientAddress}-${new Date().getTime()}`

// Note: the network name might not always line up with Transak. A conversion function might be necessary
const network = findSupportedNetwork(chain)
const networkName = network?.name.toLowerCase()
const transakLink = `${transakApiUrl}?apiKey=${transakApiKey}&isNFT=true&calldata=${transakCallData}&contractId=${transakConfig?.contractId}&cryptoCurrencyCode=${currencySymbol}&estimatedGasLimit=${estimatedGasLimit}&nftData=${transakNftData}&walletAddress=${recipientAddress}&disableWalletAddressForm=true&partnerOrderId=${partnerOrderId}&network=${networkName}`
const transakLink = transakLinkData?.url

return {
error: null,
error: errorTransakLink,
data: {
iframeId: TRANSAK_IFRAME_ID,
paymentUrl: transakLink,
Expand All @@ -212,7 +235,7 @@ export const useCreditCardPayment = ({
<TransakEventListener onSuccess={onSuccess} onError={onError} isLoading={isLoading} iframeRef={iframeRef} />
)
},
isLoading: false
isLoading: isLoadingTransakLink
}
}

Expand Down
4 changes: 0 additions & 4 deletions packages/checkout/src/hooks/useSelectPaymentModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,6 @@ type UseSelectPaymentModalReturnType = {
* currencyAddress,
* collectionAddress,
* creditCardProviders: ['sardine', 'transak'],
* transakConfig: {
* contractId: 'your-contract-id',
* apiKey: 'your-api-key'
* },
* copyrightText: 'ⓒ2024 Your Company',
* onSuccess: (txnHash: string) => {
* console.log('success!', txnHash)
Expand Down
18 changes: 18 additions & 0 deletions packages/checkout/src/hooks/useTransakWidgetUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useQuery } from '@tanstack/react-query'
import { getTransakWidgetUrl, type TransakWidgetUrlArgs } from '../api/data.js'
import { useConfig } from '@0xsequence/hooks'

export const useTransakWidgetUrl = (args: TransakWidgetUrlArgs, disabled?: boolean) => {
const { env, projectAccessKey } = useConfig()

// const apiUrl = env.apiUrl

const apiUrl = 'http://localhost:4422'

return useQuery({
queryKey: ['transakWidgetUrl', args],
queryFn: () => getTransakWidgetUrl(apiUrl, projectAccessKey, args),
staleTime: 5 * 60 * 1000,
enabled: !disabled && args.walletAddress !== ''
})
}
2 changes: 1 addition & 1 deletion packages/checkout/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { type TransactionStatusSettings } from './contexts/TransactionStatusModa
export { useTransactionStatusModal } from './hooks/useTransactionStatusModal.js'

// utils
export { fetchTransakSupportedCountries, getTransakLink } from './utils/transak.js'
export { fetchTransakSupportedCountries } from './utils/transak.js'

// OnRampProvider
export { TransactionOnRampProvider } from '@0xsequence/marketplace'
Loading