> For the complete documentation index, see [llms.txt](https://docs.fuul.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fuul.xyz/developer-guide/claiming-onchain-rewards/svm-solana.md).

# SVM (Solana)

This guide shows how to claim onchain token rewards from the Fuul protocol on Solana using the `@fuul/sdk-solana` package. The flow is: [get claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks.md) from the API, initialize the Solana SDK, build claim instructions, and submit the transaction.

{% hint style="info" %}
Before following this guide, make sure you've [fetched your claim checks](/developer-guide/claiming-onchain-rewards/get-claim-checks.md) using `@fuul/sdk`.
{% endhint %}

## Prerequisites

| Requirement         | Version | Notes                                      |
| ------------------- | ------- | ------------------------------------------ |
| Node.js             | >= 18   | Required for native crypto support         |
| TypeScript          | >= 5.0  | Strict mode recommended                    |
| `@solana/web3.js`   | ^1.95.0 | Peer dependency                            |
| `@coral-xyz/anchor` | ^0.30.0 | Peer dependency                            |
| Wallet adapter      | Any     | `@solana/wallet-adapter-react` recommended |

```bash
npm install @fuul/sdk-solana @solana/web3.js @coral-xyz/anchor
```

## 1. Configure bundler (Next.js)

The SDK uses Node.js `Buffer` API. Configure webpack to provide it in the browser.

```typescript
// next.config.ts
import type { NextConfig } from 'next';
const webpack = require('webpack');

const nextConfig: NextConfig = {
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        fs: false,
        net: false,
        tls: false,
        buffer: require.resolve('buffer/'),
      };
      config.plugins.push(
        new webpack.ProvidePlugin({
          Buffer: ['buffer', 'Buffer'],
        })
      );
    }
    return config;
  },
};

export default nextConfig;
```

{% hint style="info" %}
You also need to install the `buffer` package: `npm install buffer`.
{% endhint %}

## 2. Initialize the SDK

```typescript
'use client';

import { Connection } from '@solana/web3.js';
import { FuulSdk, Network } from '@fuul/sdk-solana';

const connection = new Connection('https://api.devnet.solana.com', {
  commitment: 'confirmed',
  confirmTransactionInitialTimeout: 60000,
});

const sdk = new FuulSdk(connection, Network.DEVNET);
```

{% hint style="warning" %}
The `Network` enum must match your RPC endpoint. Using `Network.DEVNET` with a mainnet RPC (or vice versa) will cause "Account does not exist" errors.
{% endhint %}

## 3. Fetch on-chain data

Before building a claim, fetch the project nonce and authorized signer from the chain:

```typescript
import * as anchor from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';

async function fetchClaimRequirements(sdk: FuulSdk, projectAddress: PublicKey) {
  const program = sdk.getProgram();

  // Fetch project account to get nonce
  const projectAccount = await program.account.project.fetch(projectAddress);
  const projectNonce = projectAccount.nonce as anchor.BN;

  // Fetch global config to get authorized signer
  const globalConfig = await sdk.getGlobalConfig();
  if (!globalConfig) {
    throw new Error('Global config not found');
  }

  const signers = globalConfig.rolesMapping.roles
    .filter((r) => Object.keys(r.role)[0] === 'signer')
    .map((r) => r.account);

  if (signers.length === 0) {
    throw new Error('No authorized signers found');
  }

  return { projectNonce, signer: signers[0], program };
}
```

## 4. Build claim instructions

Construct the claim message from the API response and get transaction instructions from the SDK:

```typescript
import {
  ClaimMessage,
  ClaimMessageData,
  MessageDomain,
  TokenType,
  ClaimReason,
} from '@fuul/sdk-solana';

async function buildClaimInstructions(
  sdk: FuulSdk,
  walletPublicKey: PublicKey,
  claim: {
    projectAddress: PublicKey;
    recipient: PublicKey;
    tokenMint: PublicKey;
    amount: bigint;
    deadline: number;
    reasonCode: number;
    proof: Uint8Array;
    signature: Uint8Array;
  }
) {
  const { projectNonce, signer, program } = await fetchClaimRequirements(
    sdk,
    claim.projectAddress
  );

  // Build message domain
  const domain = new MessageDomain({
    programId: program.programId,
    version: 1,
    deadline: BigInt(claim.deadline),
  });

  // Build claim data
  const claimData = new ClaimMessageData({
    amount: claim.amount,
    project: claim.projectAddress,
    recipient: claim.recipient,
    tokenType: TokenType.FungibleSpl,
    tokenMint: claim.tokenMint,
    proof: Buffer.from(claim.proof),
    reason:
      claim.reasonCode === 0
        ? ClaimReason.AffiliatePayout
        : ClaimReason.EndUserPayout,
  });

  const claimMessage = new ClaimMessage({ data: claimData, domain });

  // SDK handles ATA creation and PDA derivation internally
  const instructions = await sdk.claim({
    authority: walletPublicKey,
    projectNonce,
    message: claimMessage,
    signatures: [
      {
        signature: claim.signature,
        signer: signer,
      },
    ],
  });

  return instructions;
}
```

{% hint style="info" %}
The SDK resolves Associated Token Accounts (ATAs), PDA addresses, compute budget, and account metas internally. **Do not** create ATAs manually — this causes `IllegalOwner` errors.
{% endhint %}

## 5. Execute the transaction

```typescript
import { Transaction, Connection, PublicKey } from '@solana/web3.js';

async function executeClaim(
  connection: Connection,
  instructions: TransactionInstruction[],
  walletPublicKey: PublicKey,
  signTransaction: (tx: Transaction) => Promise<Transaction>
): Promise<string> {
  const transaction = new Transaction();
  transaction.add(...instructions);

  const { blockhash, lastValidBlockHeight } =
    await connection.getLatestBlockhash('confirmed');

  transaction.recentBlockhash = blockhash;
  transaction.lastValidBlockHeight = lastValidBlockHeight;
  transaction.feePayer = walletPublicKey;

  // Simulate first (recommended for debugging)
  const simulation = await connection.simulateTransaction(transaction);
  if (simulation.value.err) {
    console.error('Simulation failed:', simulation.value.logs);
    throw new Error(`Simulation failed: ${JSON.stringify(simulation.value.err)}`);
  }

  // Sign and send
  const signedTx = await signTransaction(transaction);
  const signature = await connection.sendRawTransaction(signedTx.serialize(), {
    skipPreflight: false,
    preflightCommitment: 'confirmed',
  });

  // Confirm
  await connection.confirmTransaction(
    { signature, blockhash, lastValidBlockHeight },
    'confirmed'
  );

  return signature;
}
```

## 6. Complete Next.js example

```typescript
'use client';

import { useWallet } from '@solana/wallet-adapter-react';
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { FuulSdk, Network } from '@fuul/sdk-solana';
import { useState } from 'react';

export function ClaimButton({ claimData }: { claimData: ValidatedClaimCheck }) {
  const { publicKey, signTransaction } = useWallet();
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');

  async function handleClaim() {
    if (!publicKey || !signTransaction) {
      alert('Please connect your wallet');
      return;
    }

    setStatus('loading');

    try {
      const connection = new Connection(
        process.env.NEXT_PUBLIC_SOLANA_RPC_URL || 'https://api.devnet.solana.com',
        'confirmed'
      );

      const sdk = new FuulSdk(connection, Network.DEVNET);

      const instructions = await buildClaimInstructions(sdk, publicKey, claimData);
      const signature = await executeClaim(
        connection,
        instructions,
        publicKey,
        signTransaction
      );

      console.log('Claim successful:', signature);
      setStatus('success');
    } catch (error) {
      console.error('Claim failed:', error);
      setStatus('error');
    }
  }

  return (
    <button onClick={handleClaim} disabled={status === 'loading'}>
      {status === 'loading' ? 'Processing...' : 'Claim Rewards'}
    </button>
  );
}
```

## Key types

```typescript
enum Network {
  FOGO_MAINNET = 'fogo-mainnet',
  FOGO_TESTNET = 'fogo-testnet',
  MAINNET = 'mainnet',
  DEVNET = 'devnet',
  TESTNET = 'testnet',
  LOCALHOST = 'localhost',
}

enum TokenType {
  Native = 'native',
  FungibleSpl = 'fungibleSpl',
  NonFungibleSpl = 'nonFungibleSpl',
}

enum ClaimReason {
  AffiliatePayout = 'affiliatePayout',
  EndUserPayout = 'endUserPayout',
}

type Signature = {
  signature: Uint8Array;
  signer: PublicKey;
};
```

## What the SDK handles internally

Do **not** pass these manually — the SDK derives them:

| What                      | How the SDK derives it                       |
| ------------------------- | -------------------------------------------- |
| Program ID                | From `Network` enum via internal IDL mapping |
| Associated Token Accounts | Created inside `sdk.claim()` if needed       |
| PDA addresses             | Derived from seeds (project, config, etc.)   |
| Global config address     | Derived from program ID                      |
| Compute budget            | Determined by SDK                            |
| Account metas             | Built from instruction requirements          |

## Troubleshooting

| Symptom                         | Cause                                     | Fix                                                                |
| ------------------------------- | ----------------------------------------- | ------------------------------------------------------------------ |
| `Buffer is not defined`         | Missing polyfill in browser               | Add webpack `ProvidePlugin` for Buffer                             |
| `IllegalOwner`                  | Manual ATA creation when SDK handles it   | Remove manual `createAssociatedTokenAccountInstruction`            |
| `Account does not exist`        | Network mismatch (devnet vs mainnet)      | Ensure `Network` enum matches your RPC endpoint                    |
| `Invalid program id`            | Hardcoded program ID                      | Use `sdk.getProgram().programId`                                   |
| `Signature verification failed` | Wrong signer or corrupted signature bytes | Use signer from `sdk.getGlobalConfig()`, verify signature encoding |
| `window is not defined`         | Importing SDK in server component         | Add `'use client'` directive or dynamic import with `ssr: false`   |
| `Blockhash not found`           | Wrong commitment level                    | Use `'confirmed'` for both connection and transaction              |
| `ConstraintSeeds`               | Wrong PDA derivation                      | Let SDK derive PDAs — don't calculate manually                     |

{% hint style="warning" %}
Always simulate the transaction before signing with `connection.simulateTransaction()`. Check `simulation.value.logs` for detailed error messages.
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.fuul.xyz/developer-guide/claiming-onchain-rewards/svm-solana.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
