Affiliate Integration
Technical guide for integrating the affiliate program
Affiliate Integration
Section titled “Affiliate Integration”Technical steps to integrate and earn from the affiliate program.
Step-by-Step Setup
Section titled “Step-by-Step Setup”-
Choose an Affiliate Code
Select a unique identifier (max 32 bytes):
// Convert string to bytesfunction stringToAffiliateCode(code: string): Uint8Array {const encoder = new TextEncoder();const bytes = encoder.encode(code);if (bytes.length > 32) {throw new Error('Affiliate code too long');}// Pad to 32 bytesconst padded = new Uint8Array(32);padded.set(bytes);return padded;}const affiliateCode = stringToAffiliateCode('your-brand'); -
Register as Affiliate
Call
create_affiliateinstruction:const [affiliatePda] = PublicKey.findProgramAddressSync([Buffer.from('Affiliate'), affiliateCode],PROGRAM_ID);await program.methods.createAffiliate({affiliateCode: Array.from(affiliateCode),feeShareBps: 1000, // 10% of protocol fees}).accounts({signer: wallet.publicKey,affiliate: affiliatePda,settings: settingsPda,systemProgram: SystemProgram.programId,}).rpc(); -
Create Token Accounts
For each token you want to receive earnings in:
const [affiliateTokenAccount] = PublicKey.findProgramAddressSync([affiliatePda.toBuffer(),TOKEN_PROGRAM_ID.toBuffer(),mint.toBuffer(),],ASSOCIATED_TOKEN_PROGRAM_ID);await program.methods.createAffiliateTokenAccount({}).accounts({signer: wallet.publicKey,affiliate: affiliatePda,affiliateTokenAccount,mint,tokenProgram: TOKEN_PROGRAM_ID,associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,systemProgram: SystemProgram.programId,}).rpc(); -
Pass Affiliate Code in Transactions
Include your affiliate PDA in deposit/withdraw transactions:
// When building deposit instructionawait program.methods.deposit({commitment: Array.from(commitment),amount: new BN(amount),}).accounts({// ... standard accountsaffiliate: affiliatePda, // Your affiliate account}).rpc(); -
Withdraw Earnings
Claim accumulated fees:
await program.methods.withdrawAffiliate().accounts({signer: wallet.publicKey,affiliate: affiliatePda,affiliateTokenAccount,destinationTokenAccount: yourWalletTokenAccount,tokenProgram: TOKEN_PROGRAM_ID,}).rpc();
Complete Integration Example
Section titled “Complete Integration Example”import { Program, AnchorProvider, BN } from '@coral-xyz/anchor';import { PublicKey, SystemProgram } from '@solana/web3.js';import { TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token';
const PROGRAM_ID = new PublicKey('FGKoWNsvTTDCGW9JyR2DJWNzSXpejWk7yXcKsFFj9GQp');
class AffiliateManager { private program: Program; private affiliateCode: Uint8Array; private affiliatePda: PublicKey;
constructor(program: Program, affiliateCodeString: string) { this.program = program; this.affiliateCode = this.stringToBytes(affiliateCodeString); [this.affiliatePda] = PublicKey.findProgramAddressSync( [Buffer.from('Affiliate'), this.affiliateCode], PROGRAM_ID ); }
private stringToBytes(str: string): Uint8Array { const bytes = new TextEncoder().encode(str); const padded = new Uint8Array(32); padded.set(bytes.slice(0, 32)); return padded; }
async register(feeShareBps: number = 1000): Promise<string> { const [settings] = PublicKey.findProgramAddressSync( [Buffer.from('Settings')], PROGRAM_ID );
const tx = await this.program.methods .createAffiliate({ affiliateCode: Array.from(this.affiliateCode), feeShareBps, }) .accounts({ signer: this.program.provider.publicKey, affiliate: this.affiliatePda, settings, systemProgram: SystemProgram.programId, }) .rpc();
return tx; }
async createTokenAccount(mint: PublicKey): Promise<string> { const [tokenAccount] = PublicKey.findProgramAddressSync( [ this.affiliatePda.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer(), ], ASSOCIATED_TOKEN_PROGRAM_ID );
const tx = await this.program.methods .createAffiliateTokenAccount({}) .accounts({ signer: this.program.provider.publicKey, affiliate: this.affiliatePda, affiliateTokenAccount: tokenAccount, mint, tokenProgram: TOKEN_PROGRAM_ID, associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, systemProgram: SystemProgram.programId, }) .rpc();
return tx; }
async getEarnings(): Promise<{ total: BN; pending: BN }> { const account = await this.program.account.affiliate.fetch(this.affiliatePda); return { total: account.totalEarnings, pending: account.pendingWithdraw, }; }
async withdraw(mint: PublicKey, destination: PublicKey): Promise<string> { const [tokenAccount] = PublicKey.findProgramAddressSync( [ this.affiliatePda.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mint.toBuffer(), ], ASSOCIATED_TOKEN_PROGRAM_ID );
const tx = await this.program.methods .withdrawAffiliate() .accounts({ signer: this.program.provider.publicKey, affiliate: this.affiliatePda, affiliateTokenAccount: tokenAccount, destinationTokenAccount: destination, tokenProgram: TOKEN_PROGRAM_ID, }) .rpc();
return tx; }
getAffiliatePda(): PublicKey { return this.affiliatePda; }}
// Usageconst affiliate = new AffiliateManager(program, 'my-app');await affiliate.register(1000); // 10% fee shareawait affiliate.createTokenAccount(usdcMint);
// Check earningsconst { pending } = await affiliate.getEarnings();console.log('Pending:', pending.toString());
// Withdrawif (pending.gt(new BN(0))) { await affiliate.withdraw(usdcMint, myWalletTokenAccount);}Best Practices
Section titled “Best Practices”| Practice | Reason |
|---|---|
| Store affiliate code securely | Losing it means losing access to earnings |
| Create token accounts proactively | For all tokens your users might use |
| Monitor earnings regularly | Catch any issues early |
| Withdraw periodically | Don’t let large balances accumulate |
Error Handling
Section titled “Error Handling”| Error | Cause | Solution |
|---|---|---|
AffiliateAlreadyExists | Code already registered | Choose different code |
InvalidFeeShare | Fee share out of range | Use 0-10000 bps |
NoEarningsToWithdraw | Zero balance | Wait for user activity |
TokenAccountNotFound | Missing token account | Create it first |