Quickstart Guide (Production)
This guide will walk you through converting $2 USD fiat into USDC stablecoin, transferring the USDC to another user, and then converting the USDC back into fiat to be sent to a fiat bank account.
Introduction
Welcome to the HIFI Quickstart Guide!
In this guide, we'll walk you through the process of converting 1 USD to USDC stablecoin, transferring the USDC to another user, and then converting the USDC back into fiat to send to a bank account. By following along, you'll gain a clear understanding of how our endpoints work and how to integrate them into your application.
To get started, you'll need an API key, which you can get by signing up in the Dashboard .
You'll have two different API keys for two different HIFI environments. Today we'll start in the Production environment with the production API key.
First, we need to convert USD to USDC.
Redirect the user to HIFI's Terms of Service page
To do this, we need to generate a signedAgreementId, which is created after the user accepts HIFI's terms of service and privacy policy on a webpage hosted by HIFI. The signedAgreementId will be used in the POST /user/create call. This will return a URL to which the user should be redirected.
As the developer, you can choose to pass a redirect URL, which will redirect the user to the URL of your choice after they accept the Terms of Service.
Request:
curl --request POST \
--url https://production.hifibridge.com/tos-link \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890' \
--header 'content-type: application/json' \
--data '
{
"idempotencyKey": "b5a913fd-7bfc-4609-ae27-3a0a46298035",
"redirectUrl": "yeahbuddy.com/path-to-redirect-page-where-you-will-parse-the-signedAgreementId"
}
'
Response:
{
"url": "https://dashboard.hifibridge.com/accept-terms-of-service?sessionToken=3e5f792f-6707-4e6b-807a-7a27b5266860&redirectUrl=yeahbuddy.com%2Fpath-to-redirect-page-where-you-will-parse-the-signedAgreementId&templateId=2fb2da24-472a-4e5b-b160-038d9dc82a40",
}
The signedAgreementId will be provided in the redirect URL as a URL parameter, which should be parsed and included in the subsequent POST /user/create call..
Note: The signedAgreementId is the same as the passed idempotencyKey you provide, so strictly speaking, you do not have to parse the redirect URL. You may simply attempt to call the POST /user/create endpoint with the idempotencyKey value as the signedAgreementId, but if the user did not accept the TOS, the user creation endpoint will return an error.
Create the user object
The user object represents an individual or business. Every future account and transaction will be tied to a user object.
Now that we have the user's signedAgreementId, we need to create the user by calling POST /user/create. This creates the user object in our system and provides HIFI with all the KYC/KYB data required for HIFI to screen the user.
Request
curl --request POST \
--url https://production.hifibridge.com/user/create \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890' \
--header 'content-type: application/json' \
--data '
{
"userType": "individual",
"legalFirstName": "Ronnie",
"legalLastName": "Coleman",
"complianceEmail": "[email protected]",
"compliancePhone": "+17025055658",
"dateOfBirth": "1964-05-13",
"taxIdentificationNumber": "431931235",
"addressLine1": "2921 S Cooper St",
"city": "Arlington",
"stateProvinceRegion": "TX",
"postalCode": "76015",
"country": "USA",
"signedAgreementId": "b5a913fd-7cfc-4609-ae27-3a0a46298037",
"ipAddress": "31.222.254.178",
"govIdFront": "https://pqgnrjvoqbopfaxmlhlv.supabase.co/storage/v1/object/sign/compliance_id/123456/front?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmwiOiJjb21wbGlhbmNlX2lkLzEyMzQ1Ni9mcm9udCIsImlhdCI6MTcyMTMxNzc2OSwiZXhwIjo0ODc0OTE3NzY5fQ.EukqQnJuoOdM8EFMlR1bePzX6r8aCGPcN6zLhT7OFPs&t=2024-07-18T15%3A49%3A29.312Z",
"govIdBack": "https://pqgnrjvoqbopfaxmlhlv.supabase.co/storage/v1/object/sign/compliance_id/123456/front?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1cmwiOiJjb21wbGlhbmNlX2lkLzEyMzQ1Ni9mcm9udCIsImlhdCI6MTcyMTMxNzc2OSwiZXhwIjo0ODc0OTE3NzY5fQ.EukqQnJuoOdM8EFMlR1bePzX6r8aCGPcN6zLhT7OFPs&t=2024-07-18T15%3A49%3A29.312Z",
"govIdCountry": "TWN"
}
'
Response
{
"wallet": {
"walletStatus": "ACTIVE",
"walletMessage": "",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"walletAddress": {
"POLYGON_MAINNET": {
"address": "0x1D6663d083C03AFCa98B2085d456542C26937e11"
},
"ETHEREUM_MAINNET": {
"address": "0x1D6663d083C03AFCa98B2085d456542C26937e11"
},
"BASE_MAINNET": {
"address": "0x1D6663d083C03AFCa98B2085d456542C26937e11"
},
"OPTIMISM_MAINNET": {
"address": "0x1D6663d083C03AFCa98B2085d456542C26937e11"
}
}
},
"user_kyc": {
"status": "PENDING",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"message": "kyc aplication still under review"
},
"ramps": {
"usdAch": {
"onRamp": {
"status": "PENDING",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"achPull": {
"achPullStatus": "PENDING",
"actionNeeded": {
"actions": [
"update"
],
"fieldsToResubmit": []
}
}
},
"offRamp": {
"status": "PENDING",
"actionNeeded": {
"actions": [
"update"
],
"fieldsToResubmit": []
}
}
},
"euroSepa": {
"onRamp": {
"status": "INACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"message": "SEPA onRamp will be available in near future"
},
"offRamp": {
"status": "PENDING",
"actionNeeded": {
"actions": [
"update"
],
"fieldsToResubmit": [
"proofOfResidency"
]
},
"message": ""
}
}
},
"user": {
"id": "accba07f-ed45-4235-a9a0-8754dbc58bdc"
}
}
Let's take a moment to understand the response:
- The
wallet
object contains the status of the wallet that was provisioned for the user and includes all of the wallet addresses associated with the user. - The
ramps
object tells you which ramps (ways to convert fiat to stablecoin and vice versa) are active for the user, each with a status. An "onramp" refers to a method by which you can convert fiat to stablecoin. An "offramp" refers to a method by which you can convert stablecoin to fiat. You'll notice that the status of every on and off ramp is set to "PENDING" in the initial POST /user/create response. This is because the compliance screening process is asynchronous (typically takes a few seconds). You'll want to poll the GET /user endpoint to get the latest status of the user before proceeding. Each on and off ramp may return afieldsToResubmit
array, which should be used to identify invalid parameters that need to be resubmitted via the PUT /user endpoint. Typically, this will be related to a user's government ID being blurry. - The
user
object contains the user's ID, which should be saved for future API calls against this particular user object. For example, adding a bank account or executing a transfer both require the userId to be passed in the query params.
After polling the GET /user endpoint for a few seconds, we see that the user has successfully passed KYC for the usdAch
offRamp
, which is all we want for the purposes of this quickstart tutorial. If this user needs to offramp to euroSepa
, we would submit one additional field (proofOfAddress
) to the PUT /user endpoint.
{
"wallet": {
"walletStatus": "ACTIVE",
"walletMessage": "",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"walletAddress": {
"POLYGON_MAINNET": {
"address": "0xe663Ab2F5aA1d37664aE8201c571F6F82D8A759f"
},
"ETHEREUM_MAINNET": {
"address": "0xe663Ab2F5aA1d37664aE8201c571F6F82D8A759f"
},
"OPTIMISM_MAINNET": {
"address": "0xe663Ab2F5aA1d37664aE8201c571F6F82D8A759f"
}
}
},
"user_kyc": {
"status": "ACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
}
},
"ramps": {
"usdAch": {
"onRamp": {
"status": "ACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"message": "",
"achPull": {
"achPullStatus": "ACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
}
}
},
"offRamp": {
"status": "ACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
}
}
},
"euroSepa": {
"onRamp": {
"status": "INACTIVE",
"actionNeeded": {
"actions": [],
"fieldsToResubmit": []
},
"message": "SEPA onRamp will be available in near future"
},
"offRamp": {
"status": "INACTIVE",
"actionNeeded": {
"actions": [
"update"
],
"fieldsToResubmit": [
"proofOfResidency"
]
},
"message": ""
}
}
},
"user": {
"id": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b"
}
}
Funding the user with $2 USDC
HIFI is actively introducing additional onramps for users in non-US geographies.
To fund the user, you can connect a bank account with HIFI by going through the onramp account set up process. To set up the onramp account with HIFI, you will first need to create a Plaid processor token and then use the processor token to set up an onramp account through HIFI's USD-Onramp_Plaid endpoint. Look here for details guide on how to create an onramp account.
For the sake of this quickstart tutorial, you can test the wallet functionality by simply sending the POLYGON_MAINNET wallet address $2 USDC on Polygon mainnet via MetaMask, Coinbase, or any other wallet you use.
Optional: Check PolygonScan to make sure that the funds were indeed received in the wallet that was just provisioned for the user we just created.
You may also notice that HIFI automagically handles gas on Polygon for any users you create. You can also verify this on the blockchain.
Transfer stablecoin to another wallet
All /transfer endpoints must be called with production api keys.
Now that the user object has been created and their wallet has been funded, let's send half of the onramped funds ($1 USDC) to another user using the POST /transfer/crypto-to-crypto endpoint.
Request
curl --request POST \
--url https://production.hifibridge.com/transfer/crypto-to-crypto \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890' \
--header 'content-type: application/json' \
--data '
{
"chain": "POLYGON_MAINNET",
"currency": "usdc",
"requestId": "c323e48f-dc47-4cb8-ab79-1aadb427c29e",
"senderUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"recipientAddress": "0x76F8fc6667E239f83a547d4e16225d6a34f6FA22",
"amount": 1
}
'
Response:
{
"transferType": "CRYPTO_TO_CRYPTO",
"transferDetails": {
"id": "6180f2db-2674-434a-8465-1c533666d595",
"requestId": "c323e48f-dc47-4cb8-ab79-1aadb427c29e",
"senderUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"recipientUserId": null,
"recipientAddress": "0x76F8fc6667E239f83a547d4e16225d6a34f6FA22",
"chain": "POLYGON_MAINNET",
"currency": "usdc",
"amount": 1,
"transactionHash": "0x458e7dbec8f255a30ff8a72e37eed004601d2ee8c2653340e20a025954d193f1",
"createdAt": "2024-07-01T18:36:38.006228+00:00",
"status": "SUBMITTED",
"contractAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"failedReason": ""
}
}
In the above example, we sent funds to a specific wallet address by providing the walletAddress parameter, but you can also specify a userId instead of the recipientAddress to send funds to another user you have created.
Transferring stablecoin on the blockchain is inherently asynchronous. Therefore, we strongly recommend polling the GET /transfer/crypto-to-crypto endpoint to confirm the success of the transfer.
Request:
curl --request GET \
--url 'https://production.hifibridge.com/transfer/crypto-to-crypto?id=6180f2db-2674-434a-8465-1c533666d595' \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1362a30026904073b4ad6383e3edea5d_789adb44'
Response:
{
"transferType": "CRYPTO_TO_CRYPTO",
"transferDetails": {
"id": "6180f2db-2674-434a-8465-1c533666d595",
"requestId": "c323e48f-dc47-4cb8-ab79-1aadb427c29e",
"senderUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"recipientUserId": null,
"recipientAddress": "0x76F8fc6667E239f83a547d4e16225d6a34f6FA22",
"chain": "POLYGON_MAINNET",
"currency": "usdc",
"transactionHash": "0x458e7dbec8f255a30ff8a72e37eed004601d2ee8c2653340e20a025954d193f1",
"createdAt": "2024-07-01T18:36:38.006228+00:00",
"updatedAt": "2024-07-01T18:37:00.722+00:00",
"status": "CONFIRMED",
"contractAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
}
}
We see that the status
is now "CONFIRMED", which indicates the transfer was successful on the blockchain.
Liquidate the remaining $1 stablecoin into fiat
HIFI supports liquidating via ACH, SEPA, or wire. We are actively adding additional payment networks.
Now that Ronnie Coleman has transferred half of his funds, we can liquidate the remaining $1 USDC to our bank account.
We first need to add a destination bank account for the stablecoin liquidation. For the purposes of this quickstart, we will add a US bank account.
Request:
curl --request POST \
--url 'https://production.hifibridge.com/account/usd/offramp?userId=ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b' \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890' \
--header 'content-type: application/json' \
--data '
{
"currency": "usd",
"accountOwnerType": "individual",
"bankName": "Chase",
"accountOwnerName": "Ronnie Coleman",
"accountNumber": "350922385",
"routingNumber": "322271427",
"streetLine1": "360 Hampton Dr",
"city": "Venice",
"state": "CA",
"postalCode": "90291",
"country": "USA"
}
'
Response:
{
"status": "ACTIVE",
"invalidFields": [],
"message": "Account created successfully",
"id": "f3ce697e-32e4-4d4a-a8ee-d0ffd738f6be"
}
Now we will liquidate $1.50 USDC to the Chase bank account we just added via the POST /transfer/crypto-to-fiat endpoint.
Request:
curl --request POST \
--url https://production.hifibridge.com/transfer/crypto-to-fiat \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890' \
--header 'content-type: application/json' \
--data '
{
"sourceCurrency": "usdc",
"destinationCurrency": "usd",
"chain": "POLYGON_MAINNET",
"paymentRail": "ach",
"sourceUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"requestId": "8f81b245-577a-4a39-b93c-5717c548e989",
"destinationUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"destinationAccountId": "f3ce697e-32e4-4d4a-a8ee-d0ffd738f6be",
"amount": 1
}
'
Response:
{
"transferType": "CRYPTO_TO_FIAT",
"transferDetails": {
"id": "f77eb6e4-8a10-4925-9c8d-0cf7ac0b563f",
"requestId": "8f81b245-577a-4a39-b93c-5717c548e989",
"sourceUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"destinationUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"chain": "POLYGON_MAINNET",
"sourceCurrency": "usdc",
"amount": 1,
"destinationCurrency": "usd",
"destinationAccountId": "f3ce697e-32e4-4d4a-a8ee-d0ffd738f6be",
"createdAt": "2024-07-01T19:16:51.31129+00:00",
"contractAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
"failedReason": "",
"transactionHash": "0xbc6efc49aa0b014bc8b71511371ebc7257507d056717f9d11c52e3be50aa870c",
"status": "SUBMITTED_ONCHAIN"
}
}
Once again, we want to poll the GET /transfer/crypto-to-fiat endpoint to make sure that the transaction was successful on the blockchain.
Request:
curl --request GET \
--url 'https://production.hifibridge.com/transfer/crypto-to-fiat?id=f77eb6e4-8a10-4925-9c8d-0cf7ac0b563f' \
--header 'accept: application/json' \
--header 'authorization: Bearer zpka_1234567890'
Response:
{
"transferType": "CRYPTO_TO_FIAT",
"transferDetails": {
"id": "f77eb6e4-8a10-4925-9c8d-0cf7ac0b563f",
"requestId": "8f81b245-577a-4a39-b93c-5717c548e989",
"sourceUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"destinationUserId": "ac1083dc-dbd9-40fe-a2aa-eb20e3ef643b",
"chain": "polygon",
"sourceCurrency": "usdc",
"amount": "1",
"destinationCurrency": "usd",
"destinationAccountId": "f3ce697e-32e4-4d4a-a8ee-d0ffd738f6be",
"transactionHash": "0xbc6efc49aa0b014bc8b71511371ebc7257507d056717f9d11c52e3be50aa870c",
"createdAt": "2024-07-01T19:16:51.31129+00:00",
"updatedAt": "2024-07-01T19:17:00.828+00:00",
"status": "COMPLETED_ONCHAIN",
"contractAddress": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
}
}
We see that the status is set to COMPLETED_ONCHAIN, which indicates a successful liquidation. Unless there is a bank-side error, we can expect the $1 to be deposited into the user's bank account in 3 business days.
Congratulations! You've successfully navigated through several key processes of converting fiat to USDC, transferring it to another user, and converting it back to fiat. We hope this guide has provided you with a solid understanding of how to utilize HIFI's API endpoints to manage digital currency transfers seamlessly. If you have any further questions or need additional support, please refer to our documentation or contact our support team. Happy coding!
Updated 3 months ago