Quick Start

Prerequisites

  • Metal Pay Connect API key. (Don't have one yet? Contact us to get started)

Step-by-Step Flow

  1. Frontend: Sends a request to the backend for a signature and initiates the authentication process.
  2. Backend: Processes the request, generates an HMAC signature using the secret key and nonce, and returns the signature to the frontend.
  3. Frontend: Receives the signature, generates a nonce, and embeds the iframe with the signature and required parameters.

Video Tutorial

Implementation Details

1. Backend Setup

Integrate these routes into your backend to ensure seamless functionality of the Metal Pay Connect frontend SDK.

Implement the following API specification:

openapi: 3.0.0
info:
  title: MPC Signature API
  description: API for generating HMAC signatures for MPC authentication
  version: 1.0.0
servers:
  - url: http://localhost:3000
    description: Local server
paths:
  /v1/signature:
    get:
      summary: Generate HMAC Signature
      description: Generates an HMAC signature using a nonce and returns it along with the nonce.
      responses:
        '200':
          description: Successful response containing the HMAC signature, nonce, apiKey.
          content:
            application/json:
              schema:
                type: object
                properties:
                  signature:
                    type: string
                    example: "generated-signature"
                    description: The generated HMAC signature
                  nonce:
                    type: string
                    example: "generated-nonce"
                    description: The generated nonce
                  apiKey:
                    type: string
                    example: "4784fae9-0a46-4a95-926d-5fea48c0a68b"
                    description: The api key
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
                    example: "Internal Server Error"
                    description: Error message
                  
const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const port = process.env.PORT || 3000;

// Secret key from environment variables
const secretKey = process.env.SECRET_KEY;
const apiKey = process.env.API_KEY;

if (!secretKey) {
  console.error('SECRET_KEY must be set in the environment');
  process.exit(1);
}

// Middleware to parse JSON bodies
app.use(bodyParser.json());

// Function to generate a nonce (e.g., a timestamp or a random string)
function generateNonce() {
  return Date.now().toString(); // You can use a more sophisticated method if needed
}

// Function to generate HMAC signature. It must be "nonce + apiKey"
function generateHMAC(nonce) {
  const hmac = crypto.createHmac('sha256', secretKey);
  hmac.update(nonce + apiKey);
  return hmac.digest('hex');
}

// Endpoint to generate HMAC signature
app.get('/v1/signature', (req, res) => {
  const nonce = generateNonce();
  const signature = generateHMAC(nonce);

  res.json({
    apiKey,
    signature,
    nonce,
  });
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}/`);
});
import { NextResponse } from 'next/server';

// Function to generate a nonce (e.g., a timestamp or a random string)
function generateNonce() {
  return Date.now().toString(); // You can use a more sophisticated method if needed
}

// Function to generate HMAC signature using Web Crypto API
async function generateHMAC(nonce: string, secretKey: string, apiKey: string) {
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(secretKey),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"]
  );
  const signature = await crypto.subtle.sign(
    "HMAC",
    key,
    new TextEncoder().encode(nonce + apiKey)
  );
  return Array.from(new Uint8Array(signature))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
}

export async function POST() {
  try {
    // Secret key from environment variables
    const secretKey = process.env.SECRET_KEY;
    const apiKey = process.env.API_KEY;

    if (!secretKey) {
      throw new Error("SECRET_KEY must be set in the environment");
    }

    const nonce = generateNonce();
    const signature = await generateHMAC(nonce, secretKey, apiKey);

    return NextResponse.json({ nonce, signature, apiKey });
  } catch (error) {
    console.error(error);
    return NextResponse.json({ error: (error as Error).message }, { status: 500 });
  }
}

Note: If you encounter a TypeScript error on line 27, ensure that your env.d.ts configuration specifies the correct type as string.

2. Frontend Setup

Step 1: Install Metal Pay Connect JS from npm:

Add metal-pay-connect-js to your project using one of the following package managers:

npm install --save metal-pay-connect-js
yarn add metal-pay-connect-js
pnpm add metal-pay-connect-js

Step 2: Integrate the Code in Your Frontend Application

Use the following examples to implement Metal Pay Connect in your frontend.

import { MetalPayConnect } from 'metal-pay-connect-js'

// Your code ...
<div id="metal-pay-connect" class="flex w-full items-center justify-center">
  </div>

const metalPayConnectEl = useRef()

useEffect(() => {
  // Initialize the SDK with configuration options
  const metalPayConnect = new MetalPayConnect({
    el: metalPayConnectEl.current,
    environment: 'dev', //type Environment = 'preview' | 'dev' | 'prod'
    params: {
      apiKey: 'YOUR_API_KEY',
      signature: 'YOUR_SIGNATURE',
      nonce: 'YOUR_NONCE'
    }
  })

  return () => {
    // Cleanup the SDK
    metalPayConnect.destroy()
  }
}, [])
import { MetalPayConnect } from 'metal-pay-connect-js'

// Your code ...
<div id="metal-pay-connect" class="flex w-full items-center justify-center">
  </div>

const metalPayConnectEl = useTemplateRef('metal-pay-connect')

// Initialize the SDK with configuration options
const metalPayConnect = ref(
  new MetalPayConnect({
    el: metalPayConnectEl.value,
    environment: 'dev', //type Environment = 'preview' | 'dev' | 'prod'
    params: {
      apiKey: 'YOUR_API_KEY',
      signature: 'YOUR_SIGNATURE',
      nonce: 'YOUR_NONCE'
    }
  })
)

onUnmounted(() => {
  // Cleanup the SDK
  metalPayConnect.value.destroy()
})
import { MetalPayConnect } from 'metal-pay-connect-js'

// Your code ...
<div id="metal-pay-connect" class="flex w-full items-center justify-center">
  </div>

const metalPayConnectEl = document.getElementById('metal-pay-connect')

// Initialize the SDK with configuration options
const metalPayConnect = new MetalPayConnect({
  el: metalPayConnectEl,
  environment: 'dev', //type Environment = 'preview' | 'dev' | 'prod'
  params: {
    apiKey: 'YOUR_API_KEY',
    signature: 'YOUR_SIGNATURE',
    nonce: 'YOUR_NONCE'
  }
})

// Cleanup the SDK
metalPayConnect.destroy()
/* We are creating our MetalPayConnectComponent and importing it into page.tsx

/* metalPayConnectComponent.tsx */
"use client";

import { MetalPayConnect } from "metal-pay-connect-js";
import { useEffect, useRef, useState } from "react";

interface Credentials {
  apiKey: string;
  signature: string;
  nonce: string;
}

export default function MetalPayConnectComponent() {
  const [credentials, setCredentials] = useState<Credentials>({
    apiKey: "",
    signature: "",
    nonce: "",
  });
  const metalPayConnectEl = useRef();
  useEffect(() => {
    if (credentials.apiKey && credentials.signature && credentials.nonce) {
      const metalPayConnect = new MetalPayConnect({
        el: metalPayConnectEl.current,
        environment: "dev",
        params: {
          apiKey: credentials.apiKey,
          signature: credentials.signature,
          nonce: credentials.nonce,
          address: { "xpr-network": "johndoe" }, // address for the user
          networks: ["xpr-network"], // List of networks to enable
        },
      });

      return () => {
        metalPayConnect.destroy();
      };
    }
  }, [credentials]);

  async function fetchCredentials() {
    try {
      const res = await fetch("/api/generate", {
        method: "POST",
      });
      const data = await res.json();
      setCredentials(data);
    } catch (error) {
      console.log(error);
    }
  }

  useEffect(() => {
    fetchCredentials();
  }, []);
  return (
    <div
      id="metal-pay-connect"
      className="flex w-full items-center justify-center"
    ></div>
  );
}

/* page.tsx */
// insert your component into your JSX:
import { MetalPayConnectComponent } from "@components/metalPayConnect";

// Your code...
<MetalPayConnectComponent />

Example Code:

Explore detailed examples for integrating with Metal Pay Connect across various frameworks and libraries: