> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ryvo.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Recipes

> End-to-end flows with @ryvonetwork/sdk, register, fund, sign, settle, withdraw.

This page is the SDK-native version of [Your First Payment](/getting-started/first-payment). Everything is expressed in terms of [`@ryvonetwork/sdk`](https://www.npmjs.com/package/@ryvonetwork/sdk) helpers. If you want the low-level protocol walkthrough, start there.

<Info>
  All examples target the live devnet deployment (program `3UyUFeNsUYPpM6hMRf7H8wg3MKEXQ82rqnsXhZrUwgSD`, chain ID `1`). Replace the `programId` and `RYVO_CHAIN_IDS.*` value for other environments.
</Info>

## Prerequisites

```bash theme={null}
npm install @ryvonetwork/sdk @coral-xyz/anchor @solana/web3.js
```

```ts theme={null}
import * as anchor from "@coral-xyz/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
import {
  RyvoClient,
  RYVO_CHAIN_IDS,
  createCommitmentMessage,
  createEd25519Instruction,
  createMultiMessageEd25519Instruction,
  createClearingRoundMessage,
  createMultiSigEd25519Instruction,
  deriveMessageDomain,
  getTokenBalance,
  nextCommitmentAmount,
} from "@ryvonetwork/sdk";

const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);

const client = new RyvoClient({ provider });
const messageDomain = deriveMessageDomain(
  client.programId,
  RYVO_CHAIN_IDS.devnet,
);
```

## 1. Register two participants

Register both sides of the payment relationship. Each owner pays a one-time registration fee defined in `GlobalConfig`.

```ts theme={null}
const alice = Keypair.generate(); // payer
const bob = Keypair.generate();   // payee

const feeRecipient = (await client.fetchGlobalConfig()).feeRecipient;

for (const signer of [alice, bob]) {
  await client
    .initializeParticipant({
      owner: signer.publicKey,
      feeRecipient,
    })
    .signers([signer])
    .rpc();
}

const aliceInfo = await client.fetchParticipant(alice.publicKey);
const bobInfo = await client.fetchParticipant(bob.publicKey);

console.log({
  alice: aliceInfo.participantId,
  bob: bobInfo.participantId,
});
```

## 2. Deposit into the vault

The payer moves tokens from their own SPL account into the protocol vault.

```ts theme={null}
// Resolve this from TokenRegistry or RYVO_PROTOCOL_DEVNET_USDC_TOKEN_ID.
const tokenId = usdcTokenId; // resolved from TokenRegistry for official devnet USDC

await client
  .deposit({
    owner: alice.publicKey,
    ownerTokenAccount: aliceUsdcAta,
    tokenId,
    amount: 10_000_000n, // 10 USDC with 6 decimals
  })
  .signers([alice])
  .rpc();

const aliceParticipant = await client.fetchParticipant(alice.publicKey);
const balance = getTokenBalance(aliceParticipant, tokenId);
console.log("Alice available:", balance.availableBalance.toString());
```

## 3. Open a channel

Channels are one-way and per-token. One channel per `(payer, payee, token)` tuple.

```ts theme={null}
await client
  .createChannel({
    owner: alice.publicKey,
    payeeOwner: bob.publicKey,
    tokenId,
    authorizedSigner: null, // or a delegated signer
  })
  .signers([alice]) // bob also needs to sign if his inbound policy is ConsentRequired
  .rpc();

const channelState = client.channelAddress(
  aliceInfo.participantId,
  bobInfo.participantId,
  tokenId,
);
```

Optionally lock funds for the payee:

```ts theme={null}
await client
  .lockChannelFunds({
    owner: alice.publicKey,
    payeeOwner: bob.publicKey,
    tokenId,
    amount: 1_000_000n,
  })
  .signers([alice])
  .rpc();
```

## 4. Direct settlement of one commitment

Alice signs a new cumulative total off-chain; Bob submits it.

```ts theme={null}
const channel = await client.fetchChannel({ channelState });

const committedAmount = nextCommitmentAmount(channel, 250_000n);

const message = createCommitmentMessage({
  messageDomain,
  payerId: aliceInfo.participantId,
  payeeId: bobInfo.participantId,
  tokenId,
  committedAmount,
});

const ed25519Ix = createEd25519Instruction(alice, message);

await client
  .settleIndividual({
    payerAccount: client.participantAddress(alice.publicKey),
    payeeAccount: client.participantAddress(bob.publicKey),
    channelState,
    submitter: bob.publicKey,
  })
  .preInstructions([ed25519Ix])
  .signers([bob])
  .rpc();
```

<Tip>
  Hold on to Alice's signed `message`. Bob does not have to submit every one, he can keep stacking fresher messages and only submit the latest, since cumulative commitments supersede earlier ones.
</Tip>

## 5. Bundle settlement

Bob settles **multiple commitments** at once, typically many signed messages from Alice, or a mix of payers who have open channels with Bob.

```ts theme={null}
const messages = [
  { signer: alice, message: createCommitmentMessage({ ...a1 }) },
  { signer: alice, message: createCommitmentMessage({ ...a2 }) },
  { signer: carol, message: createCommitmentMessage({ ...c1 }) },
];

const bundleIx = createMultiMessageEd25519Instruction(messages);

await client
  .settleCommitmentBundle({
    count: messages.length,
    payeeAccount: client.participantAddress(bob.publicKey),
    submitter: bob.publicKey,
  })
  .preInstructions([bundleIx])
  .signers([bob])
  .rpc();
```

The on-chain program walks each Ed25519 signature in order, finds the corresponding channel, and applies the new cumulative. The number passed as `count` must match the number of entries in the pre-instruction.

## 6. Clearing round (multi-party channel advancement)

Clearing rounds compress many channel settlements between N participants into a single transaction that every participant signs.

```ts theme={null}
const roundMessage = createClearingRoundMessage({
  messageDomain,
  tokenId,
  blocks: [
    { participantId: aliceInfo.participantId, entries: [{ payeeRef: 1, targetCumulative: 800_000n }] },
    { participantId: bobInfo.participantId,   entries: [{ payeeRef: 2, targetCumulative: 200_000n }] },
    { participantId: carolInfo.participantId, entries: [{ payeeRef: 0, targetCumulative: 300_000n }] },
  ],
});

const roundIx = createMultiSigEd25519Instruction(
  [alice, bob, carol],
  roundMessage,
);

await client
  .settleClearingRound({ submitter: submitterKp.publicKey })
  .preInstructions([roundIx])
  .signers([submitterKp])
  .rpc();
```

`payeeRef` is a 0-based index into the participant list in the order the round is signed. See [Settlement → Clearing rounds](/settlement-modes/clearing-rounds) for the full protocol behaviour.

## 7. Withdraw

Requests and executes the timelocked withdrawal flow. Fees are routed to `feeRecipientTokenAccount`.

```ts theme={null}
await client
  .requestWithdrawal({
    owner: alice.publicKey,
    withdrawalDestination: aliceUsdcAta,
    tokenId,
    amount: 2_000_000n,
  })
  .signers([alice])
  .rpc();

// …wait out the unlock timelock defined in GlobalConfig…

await client
  .executeWithdrawalTimelocked({
    tokenId,
    participantAccount: client.participantAddress(alice.publicKey),
    withdrawalDestination: aliceUsdcAta,
    feeRecipientTokenAccount,
  })
  .rpc();
```

To abort a pending request before the timelock fires:

```ts theme={null}
await client
  .cancelWithdrawal({ owner: alice.publicKey, tokenId })
  .signers([alice])
  .rpc();
```

## 8. Authorized signer delegation

If Alice wants a service key to sign commitments on her behalf (without giving it custody of funds), pass the delegate when creating the channel:

```ts theme={null}
const signerKp = Keypair.generate();

await client
  .createChannel({
    owner: alice.publicKey,
    payeeOwner: bob.publicKey,
    tokenId,
    authorizedSigner: signerKp.publicKey,
  })
  .signers([alice])
  .rpc();

// From now on, commitments are signed by signerKp, not alice:
const ed25519Ix = createEd25519Instruction(signerKp, message);
```

This delegation changes who signs commitments. It does not change custody or settlement guarantees.

## 9. Inbound channel policy

Payees can gate who is allowed to open channels to them:

```ts theme={null}
import { INBOUND_CHANNEL_POLICY } from "@ryvonetwork/sdk";

await client
  .updateInboundChannelPolicy({
    owner: bob.publicKey,
    inboundChannelPolicy: INBOUND_CHANNEL_POLICY.ConsentRequired,
  })
  .signers([bob])
  .rpc();
```

Under `ConsentRequired`, `createChannel` now requires Bob's signature as well.

## Where to go next

<CardGroup cols={2}>
  <Card title="Messages" icon="signature" href="/sdk/messages">
    Full details on the message layout and each Ed25519 helper.
  </Card>

  <Card title="Instructions reference" icon="list" href="/reference/instructions">
    Every on-chain instruction, its accounts, and its semantics.
  </Card>

  <Card title="Settlement modes" icon="layer-group" href="/settlement-modes/overview">
    When to pick direct, bundle, or clearing-round settlement.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/reference/errors">
    The full protocol error code table.
  </Card>
</CardGroup>
