TABLE OF CONTENTS
In this article, we explore the essential components of an API specification, including its structure, endpoints, and data formats.
API Specification
Obtain current exchange rates
The GET /rates request allows the Wallet App to obtain current exchange rates or transaction fees from APS. By accessing this information, the wallet can make an informed decision about proceeding with APS for specific transactions.
Example request:
curl --location 'https://web3.aps.money/gollum/api/v1/rates?direction=on-ramp&blockchain=stellar&asset_in=USD&asset_out=USDC&amount_in=100'
Example response:
{ "amount_in": "100", "amount_out": "95.5", "asset_in": "BUSD_BSC_TEST", "asset_out": "usd", "blockchain": "ethereum", "direction": "off-ramp", "expires_at": "2024-12-06T12:03:01Z" }
Response body contains the following elements:
Name | Type | Description |
---|---|---|
direction | string | Which direction it will be? |
blockchain | string | Which blockchain do you intend to use? |
amount_in | string | How many tokens do you want to send? |
asset_in | string | Which asset do you want to send? Example, USDC |
asset_out | string | Which asset do you want to receive? Example, USD |
Response error examples:
Code | Description |
---|---|
400 | bad request |
401 | unauthorized |
403 | forbidden |
409 | conflict |
500 | something went wrong |
/decision-flow request overview
/Decision-flow is a web page on which the customer goes through the steps of preparing a payment.
The external wallet populates a JSON object with specific parameters and signs it using its private RSA key. The resulting JSON Web Token (JWT) is sent as a query-parameter signature. On our side, we expect the filled parameters, specifically structured as follows:
Field | Description |
---|---|
flow_type | The type of transaction flow, which can be either on-ramp (converting fiat to cryptocurrency) or off-ramp (converting cryptocurrency to fiat) |
blockchain | The blockchain network being used. For example: - stellar |
base_currency_code | For on-ramp transaction it is code of fiat. For off-ramp transaction it is code of cryptocurrency. Currencies should be in the form: scheme:name:issuer, issuer is optional. Example: stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZV - the USDC dollar on the Stellar blockchain |
base_currency_amount | The amount of the base currency involved in the transaction. |
quote_currency_code | For on-ramp transaction it is code of fiat. For off-ramp transaction it is code of cryptocurrency. Currencies should be in the form: scheme:name:issuer, issuer is optional. Example: iso4217:USD - fiat dollar |
wallet_address | The Blockchain address where funds will be sent (required for on-ramp transactions). |
redirect_url | The URL to which users will be redirected after the transaction is completed. |
{ "exp": 1728208421, "payload": { "blockchain": "stellar", "flow_type": "off-ramp", "base_currency_code": "stellar:USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN", "base_currency_amount": "15", "quote_currency_code": "iso4217:USD", "wallet_address": "GCGBYAZA2C3A62ZVBRVCRE65W7RWAA7F5H4RL3JWU56ZDQ63UJKFSMYJ", "redirect_url": "http://localhost/callback" } }
Get information about a transaction
The GET /transaction-info request enables the Wallet App to retrieve the status of a completed transaction after payment processing. This request provides essential details, ensuring that the wallet can confirm transaction outcomes and update users accordingly.
Example request:
curl --location 'https://web3.aps.money/gollum/api/v1/transaction-info/a0ff618a-bdf3-11ef-8476-864889f7d0a8'
Example response:
{ "amount_in": "18.54", "amount_in_asset": "stellar:USDC:GABW6Q2Z6...", "amount_out": "18.54", "amount_out_asset": "iso4217:USD", "status": "string", "postMessage": "string", "kind": "deposit" }
Response error examples:
Code | Description |
---|---|
404 | transaction not found |
500 | something went wrong |
Generate a 2048-bit RSA private key through openssl
openssl genrsa -out private_key.pem 2048 openssl rsa -in private_key.pem -out jwtRS256.key -traditional
Code example on Golang
package main import ( "crypto/rsa" "crypto/x509" "encoding/pem" "errors" "io" "os" "time" "github.com/golang-jwt/jwt" ) type signaturePayload struct { Blockchain string `json:"blockchain"` FlowType string `json:"flow_type"` BaseCurrencyCode string `json:"base_currency_code"` BaseCurrencyAmount string `json:"base_currency_amount"` QuoteCurrencyCode string `json:"quote_currency_code"` WalletAddress string `json:"wallet_address,omitempty"` RedirectURL string `json:"redirect_url"` } const pathPrivateKey = "jwtRS256.key" func main() { privateKey, err := loadRSAPrivateKey(pathPrivateKey) if err != nil { panic(err) } payload := signaturePayload{ Blockchain: "ethereum", FlowType: "off-ramp", BaseCurrencyCode: "eth:USDC", BaseCurrencyAmount: "15", QuoteCurrencyCode: "iso4217:USD", WalletAddress: "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38", RedirectURL: "http://localhost:8000/callback", } var signature string if signature, err = makeSignature(privateKey, payload); err != nil { panic(err) } println("signature: \n", signature) } func loadRSAPrivateKey(path string) (*rsa.PrivateKey, error) { privateKeyFile, err := os.Open(path) if err != nil { return nil, err } defer privateKeyFile.Close() pemBytes, err := io.ReadAll(privateKeyFile) if err != nil { return nil, err } block, _ := pem.Decode(pemBytes) if block == nil || block.Type != "RSA PRIVATE KEY" { return nil, errors.New("failed to decode PEM block containing private key") } privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } return privateKey, nil } func makeSignature(privateKey *rsa.PrivateKey, payload signaturePayload) (string, error){ token := jwt.NewWithClaims(jwt.SigningMethodRS256, jwt.MapClaims{ "payload": payload, "exp": time.Now().Add(time.Minute * 30).Unix(), }) return token.SignedString(privateKey) }
The result of this command will be the signature - encoded token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzA4MDcyODMsInBheWxvYWQiOnsiYmxvY2tjaGFpbiI6ImV0aGVyZXVtIiwiZmxvd190eXBlIjoib2ZmLXJhbXAiLCJiYXNlX2N1cnJlbmN5X2NvZGUiOiJldGg6VVNEQyIsImJhc2VfY3VycmVuY3lfYW1vdW50IjoiMTUiLCJxdW90ZV9jdXJyZW5jeV9jb2RlIjoiaXNvNDIxNzpVU0QiLCJ3YWxsZXRfYWRkcmVzcyI6IjB4ODA3Y0Y5QTc3MmQ1YTNmOUNlRkJjMTE5MmU5MzlENjJmMEQ5YkQzOCIsInJlZGlyZWN0X3VybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9jYWxsYmFjayJ9fQ.VsEjAfOWq6da0iqGS38yguNxxmJ77wwmX7HCXdbc4hfUrHRfSfeIqaLCopWBbWSu5VBhBpleImxosRDfiqpiurwq1Bfb1rUHHSg1um1iIhG77onBgXDPvn99Aghi7-JzF62UPZEsAPqUIrvboxuUMd05KaajEkKqWbVlJR62e5UsoFrpZ8eEfnh5vmjRyra_BfvSvkbmOgMHJ6YP00bFmyXOKXILTPrH0yEsPn6xfWBTSeckUm4BM291OqVlY0Sdjsv5_ieR8ycvRNiVMVA34b5-6dlJl14LIcRwgVXHH0goTaIVL6dXz71ncHBX_YbSnJY3I3Qr23p8o38M8TNLWQMMnZRPFuCLnX70H8JZeFYvWj5RoZWJH9uKtxrsdjx_9cvBv3f2O8NQSJbf04e0avNGvOrVJG7SpYXtFXn7cn8mRXpE-we3AofX88mblS5zOF0yNDhL1w3VsO0rQlZkGNxktk9yW75FxUzECZh2ZGjgtzmT2Xm5R-8uxgRlB_X-uu-iDFjDhTJbYh-d7IKHRHTkuRPWPp433oJI5Iv22i2FIBNQen3fyIFFz69OnPhv4vtVNUXE648KYNK-6JGZGQWv5gXxQEHOmp9KcJojIzobfOMfR3SQzzQsMI5MNxTBLO7MaEWTEhH1Po3LKw8JVxcMy9u4ybJoH4DH4Sd6bfM
that corresponds to a decoded object:
{ "exp": 1730807283, "payload": { "blockchain": "ethereum", "flow_type": "off-ramp", "base_currency_code": "eth:USDC", "base_currency_amount": "15", "quote_currency_code": "iso4217:USD", "wallet_address": "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38", "redirect_url": "http://localhost:8000/callback" } }
Code example on JavaScript
const fs = require('fs') const path = require('path') const jwt = require('jsonwebtoken'); const privateKey = fs.readFileSync(path.join(__dirname, 'jwtRS256.key'), 'utf8') const current_time = Math.floor(Date.now() / 1000); const expiration_time = current_time + (60 * 30); // 30m const claims = { 'exp': expiration_time, 'payload': { 'blockchain': 'ethereum', 'flow_type': 'off-ramp', 'base_currency_code': 'eth:USDC', 'base_currency_amount': '15', 'quote_currency_code': 'iso4217:USD', 'wallet_address': '0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38', 'redirect_url': 'http://localhost:8000/callback', } }; const jwt_token = jwt.sign(claims, privateKey, { algorithm: 'RS256'}); console.log(jwt_token);
The result of this command will be the signature - encoded token:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzA4MDc0MTksInBheWxvYWQiOnsiYmxvY2tjaGFpbiI6ImV0aGVyZXVtIiwiZmxvd190eXBlIjoib2ZmLXJhbXAiLCJiYXNlX2N1cnJlbmN5X2NvZGUiOiJldGg6VVNEQyIsImJhc2VfY3VycmVuY3lfYW1vdW50IjoiMTUiLCJxdW90ZV9jdXJyZW5jeV9jb2RlIjoiaXNvNDIxNzpVU0QiLCJ3YWxsZXRfYWRkcmVzcyI6IjB4ODA3Y0Y5QTc3MmQ1YTNmOUNlRkJjMTE5MmU5MzlENjJmMEQ5YkQzOCIsInJlZGlyZWN0X3VybCI6Imh0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9jYWxsYmFjayJ9LCJpYXQiOjE3MzA4MDU2MTl9.wyO7sbsYcnxbh3IvC3QiINONIsGm7ECrlFiWkd3hPWT0bhujBbVyGra20s_IotyC-bE1gewwN2OYaMOX-J_9SD37H4U35azxz0wf7VvNJXe7m_bi7wMud_5umAM-OlUSbS7ORk0_3HPZbADINan7_9cM-UmGwpPvVFpH3RNmh6NTfD7AQH7jkxyLIk_ljbRRbgUGMYwTLjvJVyhjgyzPqspWZuvtZhsRQLazuG_mbDU6cZtJiwinoEHycm7p4mn05DvvBShEKFLlBb-Rp8EySVtzcxBlOYV9Mv-0Q8ze_t_gEAw1WCo9ywFXk67fD8JcAN4CSTndMsaJV6_moweA4zp9AL2e54R1bRgEexqOBhLOd4tVcX_DcPMhZjnxCPBWNGs4OS2KL2g9qNWqk4wZPFXl1rJIMkhaxJ5gX8GU4frrHt3qJjp53UkAEuWhlp6u_T_GalEI56qmtDE2DGCqWK8MmGmhv2jeIjft7cEevi8PbQXUrwLU7btv0l2sN1LDNnYWCm7XvxRgHLhVzf6AGUnOyrOoPOWunJP78WpgV1-qdM1vmC-gJmVWRZTSQthnPI7ARbcGfZfphH4PsBjcKVgXkMrtYF2RfcN7Ym_lYVyNe8sWJURIGvPQsq2AzxmcFaIhaJWQbzOzMTbe5MHvSNTMPb6KWFarwVyMnoWWA80
that corresponds to a decoded object:
{ "exp": 1730807419, "payload": { "blockchain": "ethereum", "flow_type": "off-ramp", "base_currency_code": "eth:USDC", "base_currency_amount": "15", "quote_currency_code": "iso4217:USD", "wallet_address": "0x807cF9A772d5a3f9CeFBc1192e939D62f0D9bD38", "redirect_url": "http://localhost:8000/callback" }, "iat": 1730805619 }
Extract the public key from the private key
openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub
Was this article helpful?
That’s Great!
Thank you for your feedback
Sorry! We couldn't be helpful
Thank you for your feedback
Feedback sent
We appreciate your effort and will try to fix the article