buddy.link
v0.7.7
Published
The only Buddy Link package you need if you are using Javascript/Typescript
Downloads
1,274
Readme
Buddy.Link SDK
The official typescript SDK for Buddy!
✨ The fast integration guide for the Buddy SDK is in the Quickstart Integration section below.
Full docs for the Buddy.Link protocol can be found at docs.buddy.link. These docs are currently being updated to match the SDK rewrite, underlying protocol concepts are the same but some code samples and patterns are still being updated.
Overview
The Buddy.Link SDK lets you add referrals and rewards to your apps and protocols incredibly fast without costly program deployments or expensive custom development. Users can generate and share referral links, referral attribution can be done invisibly or through custom referral pages, and rewards are paid out instantly to users or can be manually allocated based on on-chain and off-chain activity.
The SDK currently supports react development, and will include react-native and other support in future releases. There are also components that act as helpers or wrapper for commonly required tools in frontends deployments.
Quickstart Integration
The following steps will get your integration online as fast as possible. For additional information, check out theSDK Reference Guide section below or use the Buddy.Link Docs.
Step 1 - add the Buddy.Link package to your project
You need to install the package before you can use it. Alternatively, add "buddy.link": "^0.1.2"
to your package.json
dependencies.
- npm:
npm install buddy.link
- yarn:
yarn add buddy.link
- bun:
bun add buddy.link
Step 2 - add BuddyState to your app
The following should be done in the highest level of your app possible. Usually this is the same file where ConnectionProvider
and WalletProvider
are added.
You can use all default values when initializing BuddyState. If you want to add custom state for your app/game or override default state values check the section on Customizing initial values of BuddyState below.
//index.js or layout.js, etc
import { initBuddyState, initialBuddyLink } from "buddy.link";
// Initialize Buddy State
initBuddyState({ ...initialBuddyLink });
Step 3 - Add the BuddyLinkWrapper
This wrapper allows the current connection and wallet to be used through the app via buddyState, as well as in the SDK so that you can reduce the code used to access these on several pages in your app. This should be done in the highest level of your app possible. Usually this is the same file where ConnectionProvider
and WalletProvider
are added.
//index.js or layout.js, etc
import {
ConnectionProvider, useConnection, useWallet,
WalletProvider,
} from "@solana/wallet-adapter-react";
import { initBuddyState, initialBuddyLink, useInitBuddyLink } from "buddy.link";
// Make sure you initBuddyState before initializing the wrapper!
initBuddyState({ ...initialBuddyLink });
const InitBuddyLinkWrapper = () => {
const { connection } = useConnection();
const wallet = useWallet();
useInitBuddyLink(connection, wallet, 'your_organization_string');
return null;
};
You now have BuddyState and BuddyLink initialized for your app, and values can be accessed anywhere they are needed. The steps below are for specific actions you may want to do in your app.
Initialize a new user
This process creates all necessary accounts and treasuries for the connected wallet, and also automatically accepts a referral if a referral link was used to arrive at the current page.
The code sample below is for an Invite component to add a button to your app where users can generate their own invite link by setting up their Buddy.Link accounts and treasuries.
./components/invite.jsx
import { useBuddyState, useBuddyLink, BUDDY_STATUS } from "buddy.link"
const Invite = () => {
const [status,] = useBuddyState(BUDDY_STATUS);
const { init } = useBuddyLink();
const handleInit = useCallback(async () => {
if (!status?.init) return
try {
const results = await init();
console.log(`buddy.init results`, results);
return true;
} catch (e) {
console.log(`buddy.init error`, e)
}
}, [status]);
return (
<button onClick={() => handleInit()}>Create Link</button>
);
}
export default Invite
Get a user's referral code and display a referral link
This code sample verifies that the user has a profile and displays text with the user's referral link.
./components/referralLink.jsx
import { useBuddyState, BUDDY_PROFILE } from "buddy.link"
const ShareLink = () => {
const [profile,] = useBuddyState(BUDDY_PROFILE);
const shareLink = useMemo(() => {
const linkString = (profile?.account?.name
? `laddercaster.com?r=${profile.account.name}`
: `laddercaster.com`);
return linkString;
}, [profile]);
return(
<p>{linkString}</p>
);
}
export default ShareLink
Customizing the initial values of BuddyState
BuddyState is a fully built state-management solution used inside the Buddy.Link SDK. You can add additional state fields to use throughout your application, as well as override the default state settings that the Buddy.Link SDK initializes during setup.
To initialize BuddyState with different values you can either override individual states or create your own initialState file with multiple new fields and parameters. You must create an initialState file to add new state fields to your app.
//initialState.js has the defaults for BuddyState
export const initialBuddyLink = {
[BUDDY_CONNECTION]: null,
[BUDDY_PUBLIC_KEY]: undefined,
[BUDDY_WALLET]: undefined,
[BUDDY_MEMBERS]: null,
[BUDDY_MEMBER]: null,
[BUDDY_OPTIONS]: {},
[BUDDY_PROFILE]: null,
[BUDDY_STATS]: null,
[BUDDY_TREASURIES]: null,
[BUDDY_TREASURY_PDA]: null,
[BUDDY_STATUS]: {},
[BUDDY_ORGANIZATION]: null,
[BUDDY_CLIENT]: undefined,
[BUDDY_MINTS]: [],
[BUDDY_LOADING]: {
// Session
isLoadingInit: false,
// Current User
isLoadingProfile: false,
isLoadingTreasuries: false,
isLoadingMember: false,
isLoadingTreasuryPDA: false,
// Current Organization
isLoadingOrganization: false,
isLoadingMints: false,
isLoadingMembers: false,
isLoadingStats: false,
// Global
isLoadingGlobalMembers: false,
}
};
First, follow the regular steps for initializing BuddyState. This is the normal code pattern in the highest level of you application, usually placed inside the ConnectionProvider and WalletProvider.
import { initBuddyState, initialBuddyLink } from "buddy.link";
// Initialize Buddy State
initBuddyState({ ...initialBuddyLink });
Then, use buddyState to set the initial value. In the example below, we are overriding the value of BUDDY_MINTS
to a set of mint addresses for SPL tokens that we want to use when our app users initialize treasuries. Setting two values for SOL and USDC here means that all pre-checks during user setup will make sure they have SOL and USDC treasuries already or will add them to the setup transaction the user signs when creating their accounts and getting their referral link.
import { useBuddyState } from "buddy.link";
const [mints, setMints] = useBuddyState(BUDDY_MINTS);
// Ensure this only runs once during your app's initial loading phase
setMints([
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC on mainnet
"11111111111111111111111111111111" // SOL on mainnet
]);
That's it! Now the BUDDY_MINTS value that is referenced through the SDK will know to look for both SOL and USDC treasuries for your users.
First, add the following two files:
initialState.jsThis file allows you to override any initial states set by the SDK. As an example, add mint addresses for any SPL tokens you want users to earn and claim.
//initialState.js
// Any keys not overridden in this file can be removed from this list.
import { BUDDY_MINTS, BUDDY_CLIENT, BUDDY_MEMBERS, BUDDY_OPTIONS,
BUDDY_ORGANIZATION, BUDDY_PROFILE, BUDDY_STATS, BUDDY_TREASURIES,
BUDDY_PUBLIC_KEY, BUDDY_CONNECTION, BUDDY_STATUS, BUDDY_WALLET } from 'buddy.link'
// Any custom keys you add to buddyState should be added to this list.
// import {
// TURN_DATA,
// ACTIVE_MODAL,
// CUSTOM_NEW_STATE_KEY
// } from "./stateIndex";
// initialState values are created from the SDK, but initial overrides can be set here. You can also add entirely new state objects here that will be accessible with BuddyState.
export const initialState = {
[BUDDY_MINTS]: [
"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC on mainnet
"11111111111111111111111111111111" // SOL on mainnet
],
// [TURN_DATA]: {"turn": 0, "season": 1},
// [ACTIVE_MODAL]: '',
// [CUSTOM_NEW_STATE_KEY]: null
}
stateIndex.jsThis file allows you to define new states that can be used in your App. As an example, manage values for active turns with TURN_DATA, track whether modals are active with an object called ACTIVE_MODAL, or add an entirely custom value like CUSTOM_NEW_STATE_KEY.
//stateIndex.js
export const TURN_DATA = "TURN_DATA";
export const ACTIVE_MODAL = "ACTIVE_MODAL";
export const CUSTOM_NEW_STATE_KEY = "CUSTOM_NEW_STATE_KEY";
Then, add the initialState object to the initialization arrays for BuddyState. This code should be in the highest level of your application, usually placed inside the ConnectionProvider and WalletProvider.
import { initBuddyState, initialBuddyLink } from "buddy.link";
import { initialState } from "./initialState";
// Initialize Buddy State
initBuddyState({
...initialBuddyLink
...initialState // Note: Later values will override earlier values when merging objects with spread syntax
});
Now, initialState values will be used to automatically override the BUDDY_MINTS as well as initialize new keys in buddyState if desired.
You can access and update the new buddyState values anywhere in your app with the following code:
import { useBuddyState, BUDDY_MINTS } from "buddy.link";
import { TURN_DATA, CUSTOM_NEW_STATE_KEY } from "./stateIndex";
const [mints, setMints] = useBuddyState(BUDDY_MINTS);
const [turnData, setTurnData] = useBuddyState(TURN_DATA);
const [customNewState, setCustomNewState] = useBuddyState(CUSTOM_NEW_STATE_KEY);
// Pattern for non-object updates:
setCustomNewState("some message you want to save in buddyState");
// Pattern for object updates:
setTurnData(prevData => ({...prevData, turn: 17}));
console.log("value of BUDDY_MINTS", mints);
console.log("value of TURN_DATA", turnData);
console.log("value of CUSTOM_NEW_STATE_KEY", customNewState);
Using the Status and Loading objects in your App
The Buddy.Link SDK manages two very important objects in BuddyState. You can use status
and loading
to handle all conditional logic around data fetching and account setup sequences in your apps.
status
is a quick way to check if accounts, organizations, and other on-chain values exist without having to access each one via BuddyState.loading
allows you to see which values are currently being fetched from chain after event triggers such as wallet or connection account changing, dependent values updating, or refreshes and resets from both frontend and the SDK.
status: {
//
init: true //initClient has run, all buddyState values should be updated
// Current User Status
hasProfile: true //current wallet has global profile
hasTreasuries: true //current wallet has treasuries
hasMember: true //current wallet has member for current organization
hasTreasuryPDA: true //current wallet has treasury for primary mint for current organization
// Current Organization Status
hasOrganization: true //current organization exists
hasMints: true //current organization has mints
hasMembers: true //current organization has members
hasStats: true //loaded stats for members
// Global Buddy Status
hasGlobalMembers: true //loaded global member count for every organization on buddylink protocol
}
loading: {
// Session
isLoadingInit: false,
// Current User
isLoadingProfile: false,
isLoadingTreasuries: false,
isLoadingMember: false,
isLoadingTreasuryPDA: false,
// Current Organization
isLoadingOrganization: false,
isLoadingMints: false,
isLoadingMembers: false,
isLoadingStats: false,
// Global
isLoadingGlobalMembers: false,
}
A short example of using status
and loading
to handle app display is shown below. For larger examples, check out the Buddy.Link Examples Repo on github.
import { useBuddyState, BUDDY_STATUS, BUDDY_LOADING, BUDDY_WALLET } from "buddy.link";
const [wallet] = useBuddyState(BUDDY_WALLET);
const [status] = useBuddyState(BUDDY_STATUS);
const [loading] = useBuddyState(BUDDY_LOADING);
// if wallet connected and accessible via BuddyState, display connected publicKey
// if SDK is still initializing, display a placeholder message.
// if the current wallet's Profile or Member values are loading, display a loading message
// if both are loaded, prompt users without a profile or member to generate their own link (initializes those accounts), otherwise display their referral link
return(
<div>
wallet && wallet.publicKey && <p>`current connected wallet is ${wallet?.publicKey?.toBase58() || '(error getting publicKey)'}</p>
<div>
{loading.isLoadingInit ? (
<p>Buddy.Link SDK is still loading</p>
) : (
{(loading.isLoadingProfile || loading.isLoadingMember) ? (
<p>Fetching your accounts from chain...</p>
) : (
{status.hasProfile && status.hasMember ? (
<p>`Your referral link is sampleapp.com/?r=${profile?.account?.name}`</p>
) : (
<p>`Use the button below to create your referral link</p>
// Add button code and handler here
)}
)}
)}
</div>
</div>
)
An example scenario about checking if state is loaded and ready to display:
- if status.init is undefined or false, buddyLink and buddyState are still initializing. Display a loading state.
- if status.init == true, buddyLink and buddyState have initialized successfully
- Then, we can check how the user should be handled in the reward flow:
- if status.hasMember or status.hasTreasuries or status.hasProfile is false, user is not setup, prompt them to generate link
- if status.hasMember and status.hasTreasuries and status.hasProfile are true, user is setup, display their link using values pulled from profile and member
- Then, we can check how the user should be handled in the reward flow:
An example scenario of controlling render logic for treasury values:
- if status.init == true , init has happened
- then if loading.isLoadingTreasuries == true, we know that treasuries are fetching, we display loading state for treasury values
- if loading.isLoadingTreasuries == false and status.hasTreasuries == false we know the user has no treasuries, we don't display balances, we prompt user to create treasuries
- if loading.isLoadingTreasuries == false and status.hasTreasuries == true we know user has treasuries and we can check specific treasuries exist and their balances
- the array of current user treasuries can be accessed with const [treasuries] = useBuddyState(BUDDY_TREASURIES); and we can filter those objects to check specific mintAddresses.
- If they exist, display balance and allow user to claim
- if they don't exist, prompt user to create a treasury for that mintAddress
- the array of current user treasuries can be accessed with const [treasuries] = useBuddyState(BUDDY_TREASURIES); and we can filter those objects to check specific mintAddresses.
Additional Components
The Buddy.Link SDK comes packaged with several helpful components to accelerate and simplify frontend development.
Components include:
- BuddyState, an opinionated state management solution that avoids complexities like desychronized changes in apps with several shared contexts.
- transactionAssembler, a transaction builder that combines your core instructions, compute and priority inputs, and optional memos. Future versions will include more options such as nonce support and versioned transactions.
- getTokenBalance and getTokenAccountBalance are helpers to get the balance of any SPL token held by a wallet including handling decimal math and missing token accounts. Future versions will include support for additional token standards.