> For the complete documentation index, see [llms.txt](https://docs.diversifi.trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.diversifi.trade/diversifi-for-partners/integration-guide/platform-integration/platform_integration_example.ts.md).

# platform\_integration\_example.ts

view on githhub : <https://github.com/SolutioFi-io/diversifi-integration-guide/blob/main/deposit_withdraw_example.ts>

```typescript
/**
 * DiversiFi Platform Integration Example
 *
 * Demonstrates how a third-party platform can execute deposits, withdrawals,
 * and refunds on behalf of its end users using a single API key with the
 * `integrations` permission.
 *
 * Flow:
 *   1. Platform backend calls /v1/integrations/deposit/tx (or withdraw/tx) with the user's wallet.
 *   2. API returns an unsigned Solana transaction + a one-time confirmToken.
 *   3. User signs and submits the transaction (e.g. via their wallet adapter).
 *   4. Platform calls POST /v1/integrations/confirm with the signature + confirmToken.
 *      This links the transaction to your API key and returns the transactionGroupId.
 *   5. Platform polls /v1/integrations/transaction/:transactionGroupId for status.
 *
 * Requirements: npm install @solana/web3.js dotenv
 */

import { createHash } from "crypto";
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";
import dotenv from "dotenv";

dotenv.config({ path: ".env" });

// ---------------------------------------------------------------------------
// Configuration — set these in your .env file
// ---------------------------------------------------------------------------
const API_BASE_URL = process.env.API_BASE_URL ?? "https://dev-api.diversifi.trade";
const API_KEY = process.env.DIVERSIFI_API_KEY ?? "";  // dfi_... with 'integrations' permission
const RPC_URL = process.env.RPC_URL ?? "https://api.mainnet-beta.solana.com";

// In production the user signs on the frontend via their wallet adapter.
// Here we load a keypair from an environment variable to simulate that step.
const USER_SECRET_KEY = process.env.USER_SECRET_KEY
    ? Uint8Array.from(JSON.parse(process.env.USER_SECRET_KEY))
    : null;

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

async function apiGet<T>(path: string, params: Record<string, string | number>): Promise<T> {
    const url = new URL(path, API_BASE_URL);
    for (const [key, value] of Object.entries(params)) {
        url.searchParams.set(key, String(value));
    }

    const response = await fetch(url.toString(), {
        headers: {
            Authorization: `Bearer ${API_KEY}`,
            "Content-Type": "application/json",
        },
    });

    if (response.status === 429) {
        const retryAfter = response.headers.get("retry-after") ?? "60";
        throw new Error(`Rate limited. Retry after ${retryAfter}s`);
    }

    const body = await response.json() as any;

    if (!response.ok || body.error) {
        throw new Error(body.error ?? body.message ?? `HTTP ${response.status}`);
    }

    return body as T;
}

async function apiPost<T>(path: string, payload: Record<string, string>): Promise<T> {
    const response = await fetch(new URL(path, API_BASE_URL).toString(), {
        method: "POST",
        headers: {
            Authorization: `Bearer ${API_KEY}`,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
    });

    if (response.status === 429) {
        const retryAfter = response.headers.get("retry-after") ?? "60";
        throw new Error(`Rate limited. Retry after ${retryAfter}s`);
    }

    const body = await response.json() as any;

    if (!response.ok || body.error) {
        throw new Error(body.error ?? body.message ?? `HTTP ${response.status}`);
    }

    return body as T;
}

/**
 * Derive the transactionGroupId from a Solana signature.
 * The backend computes: sha256(signature).hex().slice(0, 16)
 *
 * Used for refunds (which have no confirm step).
 * For deposits and withdrawals, prefer the ID returned by POST /confirm.
 */
function deriveTransactionGroupId(signature: string): string {
    return createHash("sha256")
        .update(signature)
        .digest("hex")
        .substring(0, 16);
}

async function signAndSubmit(
    connection: Connection,
    signer: Keypair,
    base64Tx: string
): Promise<string> {
    const txBytes = Buffer.from(base64Tx, "base64");
    const tx = VersionedTransaction.deserialize(txBytes);
    tx.sign([signer]);
    const signature = await connection.sendRawTransaction(tx.serialize());
    await connection.confirmTransaction(signature, "confirmed");
    return signature;
}

// ---------------------------------------------------------------------------
// Platform deposit: USDC → LP tokens
// ---------------------------------------------------------------------------

/**
 * Initiates a deposit on behalf of an end user.
 *
 * @param userWallet     The end user's Solana wallet address (base58)
 * @param basketAddress  The basket address / Index PDA (base58)
 * @param usdcAmount     Human-readable USDC amount (e.g. 100.5 for $100.50)
 * @returns              transactionGroupId — use this to poll status
 */
export async function platformDeposit(
    connection: Connection,
    userSigner: Keypair,
    userWallet: string,
    basketAddress: string,
    usdcAmount: number
): Promise<string> {
    console.log(`Fetching unsigned deposit transaction (${usdcAmount} USDC → ${basketAddress})...`);

    const { data } = await apiGet<{ data: { unsignedTxs: string[]; confirmToken: string } }>(
        "/v1/integrations/deposit/tx",
        { basket_address: basketAddress, amount: usdcAmount, user_wallet: userWallet }
    );

    const { confirmToken } = data;
    let lastSignature = "";

    for (const unsignedTx of data.unsignedTxs) {
        // In production: present the transaction to the user's wallet adapter for signing.
        // Here we sign directly with the loaded keypair for demonstration.
        console.log("Signing and submitting transaction...");
        lastSignature = await signAndSubmit(connection, userSigner, unsignedTx);
        console.log(`  Submitted: ${lastSignature}`);
    }

    // Confirm: links the on-chain signature to your API key and returns the transactionGroupId.
    // Without this step the transaction processes but cannot be retrieved via GET /transaction/:id.
    console.log("Confirming with DiversiFi API...");
    const { data: confirmData } = await apiPost<{ data: { transactionGroupId: string } }>(
        "/v1/integrations/confirm",
        { signature: lastSignature, confirmToken }
    );

    const { transactionGroupId } = confirmData;
    console.log(`Deposit confirmed. transactionGroupId: ${transactionGroupId}`);
    return transactionGroupId;
}

// ---------------------------------------------------------------------------
// Platform withdrawal: LP tokens → USDC
// ---------------------------------------------------------------------------

/**
 * Initiates a withdrawal on behalf of an end user.
 *
 * @param userWallet     The end user's Solana wallet address (base58)
 * @param basketAddress  The basket address / Index PDA (base58)
 * @param lpAmount       Human-readable LP token amount to withdraw
 * @returns              transactionGroupId — use this to poll status
 */
export async function platformWithdraw(
    connection: Connection,
    userSigner: Keypair,
    userWallet: string,
    basketAddress: string,
    lpAmount: number
): Promise<string> {
    console.log(`Fetching unsigned withdrawal transaction (${lpAmount} LP tokens from ${basketAddress})...`);

    const { data } = await apiGet<{ data: { unsignedTxs: string[]; confirmToken: string } }>(
        "/v1/integrations/withdraw/tx",
        { basket_address: basketAddress, amount: lpAmount, user_wallet: userWallet }
    );

    const { confirmToken } = data;
    let lastSignature = "";

    for (const unsignedTx of data.unsignedTxs) {
        console.log("Signing and submitting transaction...");
        lastSignature = await signAndSubmit(connection, userSigner, unsignedTx);
        console.log(`  Submitted: ${lastSignature}`);
    }

    // Confirm: links the on-chain signature to your API key and returns the transactionGroupId.
    console.log("Confirming with DiversiFi API...");
    const { data: confirmData } = await apiPost<{ data: { transactionGroupId: string } }>(
        "/v1/integrations/confirm",
        { signature: lastSignature, confirmToken }
    );

    const { transactionGroupId } = confirmData;
    console.log(`Withdrawal confirmed. transactionGroupId: ${transactionGroupId}`);
    return transactionGroupId;
}

// ---------------------------------------------------------------------------
// Platform refund: cancel a stuck deposit or withdrawal
// ---------------------------------------------------------------------------

/**
 * Cancels a pending deposit or withdrawal and reclaims the locked tokens.
 *
 * @param userWallet     The end user's Solana wallet address (base58)
 * @param basketAddress  The basket address / Index PDA (base58)
 * @returns              transactionGroupId of the refund transaction, or null if nothing to refund
 */
export async function platformRefund(
    connection: Connection,
    userSigner: Keypair,
    userWallet: string,
    basketAddress: string
): Promise<string | null> {
    const { data: eligibility } = await apiGet<{
        data: {
            canRefund: boolean;
            refundType: "deposit" | "withdraw" | null;
        }
    }>("/v1/integrations/refund/check", { basket_address: basketAddress, user_wallet: userWallet });

    if (!eligibility.canRefund || !eligibility.refundType) {
        console.log("No refundable balance found.");
        return null;
    }

    console.log(`Refunding ${eligibility.refundType}...`);

    const { data: refundData } = await apiGet<{ data: { unsignedTx: string } }>(
        "/v1/integrations/refund/tx",
        { basket_address: basketAddress, user_wallet: userWallet, refund_type: eligibility.refundType }
    );

    const signature = await signAndSubmit(connection, userSigner, refundData.unsignedTx);
    console.log(`  Submitted refund: ${signature}`);

    const transactionGroupId = deriveTransactionGroupId(signature);
    console.log(`Refund submitted. transactionGroupId: ${transactionGroupId}`);
    return transactionGroupId;
}

// ---------------------------------------------------------------------------
// Poll transaction status
// ---------------------------------------------------------------------------

type TransactionState =
    | "initialized"
    | "executing"
    | "completed"
    | "partially_completed"
    | "refunded"
    | "failed";

const TERMINAL_STATES = new Set<TransactionState>(["completed", "partially_completed", "refunded", "failed"]);

/**
 * Polls /v1/integrations/transaction/:transactionGroupId until the transaction
 * reaches a terminal state or the timeout is exceeded.
 *
 * @param transactionGroupId  Derived from deriveTransactionGroupId(signature)
 * @param timeoutMs           Max wait time in milliseconds (default: 5 minutes)
 * @param intervalMs          Poll interval in milliseconds (default: 5 seconds)
 */
export async function pollTransactionStatus(
    transactionGroupId: string,
    timeoutMs = 5 * 60 * 1000,
    intervalMs = 5_000
): Promise<{ state: TransactionState; data: any }> {
    const deadline = Date.now() + timeoutMs;

    while (Date.now() < deadline) {
        const { data } = await apiGet<{ data: { state: TransactionState } & any }>(
            `/v1/integrations/transaction/${transactionGroupId}`,
            {}
        );

        console.log(`  [${transactionGroupId}] state: ${data.state}`);

        if (TERMINAL_STATES.has(data.state)) {
            return { state: data.state, data };
        }

        await new Promise(resolve => setTimeout(resolve, intervalMs));
    }

    throw new Error(`Transaction ${transactionGroupId} did not reach terminal state within ${timeoutMs}ms`);
}

// ---------------------------------------------------------------------------
// Demo
// ---------------------------------------------------------------------------

const isMain =
    typeof require !== "undefined" && require.main === module ||
    (typeof process !== "undefined" && process.argv[1]?.endsWith("platform_integration_example.ts"));

if (isMain) {
    if (!API_KEY) {
        console.error("Set DIVERSIFI_API_KEY in your .env file (requires 'integrations' permission).");
        process.exit(1);
    }
    if (!USER_SECRET_KEY) {
        console.error("Set USER_SECRET_KEY in your .env file (JSON array of 64 bytes).");
        process.exit(1);
    }

    const connection = new Connection(RPC_URL, "confirmed");
    const userSigner = Keypair.fromSecretKey(USER_SECRET_KEY);
    const userWallet = userSigner.publicKey.toBase58();
    const BASKET = "HfrGnAvWjkNJCbixiVf9zRAbCZP8BKh2tWqtFqsGsdY1"; // example basket address (Index PDA) for dToken ADdkaLqDsupDXi3qAEpuv9LXUSjA25ypcKGRseejLXaz

    (async () => {
        try {
            console.log(`User wallet: ${userWallet}\n`);

            // --- Deposit 10 USDC ---
            const depositGroupId = await platformDeposit(connection, userSigner, userWallet, BASKET, 10);
            const { state, data } = await pollTransactionStatus(depositGroupId);
            console.log(`\nDeposit final state: ${state}`);
            if (data.finalIndexTokenBalance) {
                console.log(`LP tokens received: ${data.finalIndexTokenBalance}`);
            }

            // --- Withdraw 5 LP tokens ---
            // const withdrawGroupId = await platformWithdraw(connection, userSigner, userWallet, BASKET, 5);
            // await pollTransactionStatus(withdrawGroupId);

            // --- Refund a stuck operation ---
            // const refundGroupId = await platformRefund(connection, userSigner, userWallet, BASKET);
            // if (refundGroupId) await pollTransactionStatus(refundGroupId);
        } catch (e) {
            console.error(e);
        }
    })();
}
```
