Skip to content

Commit

Permalink
Add recipient claim in SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
DogLooksGood committed Jan 1, 2024
1 parent 3b7b960 commit 7e31507
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Race Protocol: A multi-chain infrastructure for asymmetric competitive games
## Features
- CLI: Update `publish` command. Now it receives the path to the WASM bundle instead of the Arweave URL to solana metadata.
- Add optional `createProfileIfNeeded` to join options.
- SDK: Add `recipientClaim` and its solana implementation.

## Fixes
- Transactor: Improve the retry mechanism for settle.
Expand Down
12 changes: 12 additions & 0 deletions js/sdk-core/src/app-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,16 @@ export class AppHelper {
return new TokenWithBalance(t, balance);
});
}

/**
* Claim the fees collected by game.
*
* @param wallet - The wallet adapter to sign the transaction
* @param gameAddr - The address of game account.
*/
async claim(wallet: IWallet, gameAddr: string): Promise<TransactionResult<void>> {
const gameAccount = await this.#transport.getGameAccount(gameAddr);
if (gameAccount === undefined) throw new Error('Game account not found');
return await this.#transport.recipientClaim(wallet, { recipientAddr: gameAccount?.recipientAddr });
}
}
73 changes: 72 additions & 1 deletion js/sdk-solana/src/instruction.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { PublicKey, SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
import { PublicKey, SYSVAR_RENT_PUBKEY, SystemProgram, TransactionInstruction } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync } from '@solana/spl-token';
import { publicKeyExt } from './utils';
import { PROGRAM_ID, METAPLEX_PROGRAM_ID, PLAYER_PROFILE_SEED } from './constants';
import { enums, field, serialize } from '@race-foundation/borsh';
import { Buffer } from 'buffer';
import { EntryType } from '@race-foundation/sdk-core';
import { RecipientSlotOwnerAssigned, RecipientState } from './accounts';

// Instruction types

Expand All @@ -21,6 +22,9 @@ export enum Instruction {
UnregisterGame = 9,
JoinGame = 10,
PublishGame = 11,
CreateRecipient = 12,
AssignRecipient = 13,
RecipientClaim = 14,
}

// Instruction data definitations
Expand Down Expand Up @@ -388,3 +392,70 @@ export function publishGame(opts: PublishGameOptions): TransactionInstruction {
data,
});
}


export type ClaimOpts = {
payerKey: PublicKey,
recipientKey: PublicKey,
recipientState: RecipientState,
};

export function claim(opts: ClaimOpts): TransactionInstruction {
const [pda, _] = PublicKey.findProgramAddressSync([opts.recipientKey.toBuffer()], PROGRAM_ID);

let keys = [
{
pubkey: opts.payerKey,
isSigner: true,
isWritable: false,
},
{
pubkey: opts.recipientKey,
isSigner: false,
isWritable: true,
},
{
pubkey: pda,
isSigner: false,
isWritable: false,
},
{
pubkey: TOKEN_PROGRAM_ID,
isSigner: false,
isWritable: false,
},
{
pubkey: SystemProgram.programId,
isSigner: false,
isWritable: false,
},
];

for (const slot of opts.recipientState.slots) {
for (const slotShare of slot.shares) {
if (slotShare.owner instanceof RecipientSlotOwnerAssigned && slotShare.owner.addr === opts.payerKey) {
keys.push({
pubkey: slot.stakeAddr,
isSigner: false,
isWritable: false,
});
const ata = getAssociatedTokenAddressSync(slotShare.owner.addr, slot.tokenAddr);
keys.push({
pubkey: ata,
isSigner: false,
isWritable: false,
})
}
}
}

if (keys.length === 5) {
return new Error('No slot to claim');
}

return new TransactionInstruction({
keys,
programId: PROGRAM_ID,
data: Uint8Array.of(Instruction.RecipientClaim),
});
}
32 changes: 30 additions & 2 deletions js/sdk-solana/src/solana-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,25 @@ export class SolanaTransport implements ITransport {
throw new Error('unimplemented');
}

async recipientClaim(_wallet: IWallet, _params: RecipientClaimParams): Promise<TransactionResult<void>> {
throw new Error('unimplemented');
async recipientClaim(wallet: IWallet, params: RecipientClaimParams): Promise<TransactionResult<void>> {
const payerKey = new PublicKey(wallet.recipientAddr);
const recipientKey = new PublicKey(params.recipientAddr);
const recipientState = await this._getRecipientState(recipientKey);

if (recipientState === undefined) {
throw new Error('Recipient account not found');
}

const recipientClaimIx = instruction.claim({
recipientKey, payerKey
});
const tx = await makeTransaction(conn, playerKey);

tx.add(recipientClaimIx);

tx.partialSign(tempAccountKeypair);

return await wallet.sendTransaction(tx, this.#conn);
}

async addCreateProfileIxToTransaction(tx: Transaction, wallet: IWallet, params: CreatePlayerProfileParams): Promise<void> {
Expand Down Expand Up @@ -650,6 +667,17 @@ export class SolanaTransport implements ITransport {
return ret;
}

async _getRecipientState(recipientKey: PublicKey): Promise<RecipientState | undefined> {
const conn = this.#conn;
const recipientAccount = await conn.getAccountInfo(recipientKey);
if (recipientAccount !== null) {
const data = recipientAccount.data;
return RecipientState.deserialize(data);
} else {
return undefined;
}
}

async _getRegState(regKey: PublicKey): Promise<RegistryState | undefined> {
const conn = this.#conn;
const regAccount = await conn.getAccountInfo(regKey);
Expand Down

0 comments on commit 7e31507

Please sign in to comment.