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

# SDK quickstart

> @dagon/payroll-sdk: encrypt salaries, put one Squads v4 proposal up for a vote, crank the run, withdraw wages and grant a view. Adapted from the SDK's Squads example.

> **CONFIDENTIAL & PROPRIETARY © 2026 Inkwell Finance, Inc. All Rights Reserved.**
>
> This document is for informational purposes only and does not constitute legal, tax, or investment advice, nor an offer to sell or a solicitation to buy any security or other financial instrument. Any examples, structures, or flows described here are design intent only and may change.

> [!WARNING]
>
> **Not on npm.** `@dagon/payroll-sdk` (version 0.1.0, BUSL-1.1) is marked
> `private` and has not been published. It lives in the Dagon monorepo at
> `sdks/payroll` and ships as TypeScript source (`src/index.ts`); the
> examples run with `bun`. The program it targets is not deployed.
>
> **Pre-alpha encryption.** `encryptAmounts` currently sends Encrypt's dev
> executor a 17-byte "mock ciphertext" that contains the plaintext value.
> It will be replaced with real client-side encryption and a proof when
> Encrypt ships them. Until Encrypt Alpha 1, salaries are not confidential.

This walks through one month of payroll with a Squads v4 (or Altitude)
multisig, adapted from `sdks/payroll/examples/squads-payroll.ts`.

## What you need

* `@solana/web3.js` and `@sqds/multisig` (the SDK's dependencies).
* Encrypt's gRPC client, `@encrypt.xyz/pre-alpha-solana-client`. The SDK
  takes it as a structural `EncryptInputClient` and does not pin a build.
* Encrypt's network encryption key (hex).
* A Squads v4 multisig, and a member key with Initiate permission for HR.
* For anyone who cranks, withdraws or creates the company: SOL and an
  **Encrypt fee deposit** (`["encrypt_deposit", payer]`, created with
  Encrypt's `create_deposit`). Multisig authority instructions never need
  one.

## 1 · Set up

```ts theme={null}
import { Connection, Keypair, PublicKey, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
import {
  ENCRYPT_DEVNET,
  EncryptInputClient,
  PayrollCrank,
  PayrollInstructions,
  SquadsV4Authority,
  UNLIMITED,
  companyIdFromSlug,
  deriveEncryptAccounts,
  encryptAmounts,
  fetchCompany,
} from "@dagon/payroll-sdk"; // unpublished: in the monorepo, import sdks/payroll/src/index.js

// Placeholders you supply:
declare const hr: Keypair;     // a Squads member with Initiate permission
declare const payer: Keypair;  // any crank payer (SOL + Encrypt deposit)
declare const mint: PublicKey; // the payroll SPL mint

const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const networkKey = Buffer.from(process.env.NETWORK_KEY!, "hex");

const squads = new SquadsV4Authority(new PublicKey(process.env.MULTISIG!));
const payroll = new PayrollInstructions();

// The Company PDA is seeded by the authority it was created for (this vault).
const company = payroll.pdas.company(squads.authority, companyIdFromSlug("acme"));
```

`squads.authority` is the Squads vault PDA (vault index 0 by default).

## 2 · Create the company (once)

Any payer creates the company. The multisig does not sign.

```ts theme={null}
const hrEnc = deriveEncryptAccounts(hr.publicKey, networkKey);
const { ix, signers } = payroll.createCompany({
  companyId: companyIdFromSlug("acme"),
  mint,                        // the payroll SPL mint
  authority: squads.authority, // the multisig vault PDA
  payer: hr.publicKey,
  encrypt: hrEnc,
});
await sendAndConfirmTransaction(connection, new Transaction().add(ix), [hr, ...signers]);
```

Policy starts empty. Put `payroll.authority.updatePolicy(...)` (auditor,
salary cap, paused) and optionally `payroll.authority.setOperator(...)` in
the multisig's first proposal.

## 3 · Encrypt salaries

```ts theme={null}
const { createEncryptClient } = (await import("@encrypt.xyz/pre-alpha-solana-client/grpc")) as {
  createEncryptClient: (url?: string) => EncryptInputClient;
};
const encryptClient = createEncryptClient(ENCRYPT_DEVNET.grpcUrl);

const hires = [
  { wallet: new PublicKey("..."), salary: 8_500_000000n },
  { wallet: new PublicKey("..."), salary: 11_250_000000n },
];
const salaryCts = await encryptAmounts(
  encryptClient,
  payroll.programId,
  networkKey,
  hires.map((h) => h.salary),
);
```

`encryptAmounts` returns one ciphertext handle per amount, authorized to
the payroll program. Amounts are `u64` in the mint's base units. The
handles are all that goes into the proposal.

## 4 · Propose one vault transaction

Hires first, then the run. The vault is both authority and rent payer.

```ts theme={null}
const c = await fetchCompany(connection, company);

const payrollIxs = [
  ...hires.map((h, i) =>
    payroll.authority.addEmployee({ company, authority: squads.authority, wallet: h.wallet, salaryCt: salaryCts[i] }),
  ),
  payroll.authority.openPayRun({
    company,
    authority: squads.authority,
    nextRunId: c.lastRunId + 1n,
    period: 202609n,
    budget: UNLIMITED, // or a public ceiling in base units
  }),
];

const { transactionIndex, ixs } = await squads.proposeIxs({
  connection,
  creator: hr.publicKey,
  payrollIxs,
  memo: "Payroll 2026-09",
});
await sendAndConfirmTransaction(connection, new Transaction().add(...ixs), [hr]);
```

Members approve and execute in the Squads app, or build the instructions
with `squads.approveIx(member, transactionIndex)` and
`squads.executeIx(connection, member, transactionIndex)`.

To fund the vault, add an SPL transfer from the Squads vault to `c.vault`
in the same proposal.

## 5 · Crank the run

After the proposal executes, anyone can crank. If an `operator` is set,
payments must come from that key.

```ts theme={null}
const crankEnc = deriveEncryptAccounts(payer.publicKey, networkKey);
const crank = new PayrollCrank(connection, payer, crankEnc, undefined, console.log);

await crank.syncTreasury(company);
const { lastRunId } = await fetchCompany(connection, company);
const result = await crank.runPayroll(company, lastRunId);
// → { paid, bonuses, closed }
```

`runPayroll` syncs the treasury, prepares the run, creates any missing
wage ledgers, pays every eligible employee and approved bonus, and closes
the run once the headcount is covered.

## 6 · Withdraw wages (employee)

```ts theme={null}
import { fetchEmployee } from "@dagon/payroll-sdk";

declare const wallet: Keypair;       // the employee
declare const recipient: PublicKey; // SPL token account of the payroll mint

const employeePda = payroll.pdas.employee(company, wallet.publicKey);
const e = await fetchEmployee(connection, employeePda);
const employeeEnc = deriveEncryptAccounts(wallet.publicKey, networkKey);

const withdraw = payroll.employee.withdraw({
  company,
  wallet: wallet.publicKey,
  balanceCt: e.balanceCt,
  recipient,
  amount: 2_000_000000n,
  payer: wallet.publicKey,
  encrypt: employeeEnc,
});
await sendAndConfirmTransaction(connection, new Transaction().add(withdraw.ix), [wallet, ...withdraw.signers]);

// Same keypair: for ~150 slots only the initiator may request decryption.
const employeeCrank = new PayrollCrank(connection, wallet, employeeEnc);
await employeeCrank.settleWithdrawal(payroll.pdas.withdrawal(employeePda));
```

`settleWithdrawal` waits for the debit to commit, requests decryption of
the 0-or-amount result, waits for the decryptor, then completes. If the
balance didn't cover `amount`, no tokens move and the debit is re-credited
inside FHE. If the payout account gets frozen or closed,
`payroll.employee.retarget(...)` swaps it.

## 7 · Grant a view

An employee proves their balance to a lender:

```ts theme={null}
import { ViewTarget } from "@dagon/payroll-sdk";

declare const lenderPubkey: PublicKey;

const view = payroll.grantView({
  target: ViewTarget.Balance,
  company,
  employee: employeePda,
  sourceCt: e.balanceCt,
  viewer: lenderPubkey,
  grantor: wallet.publicKey,
  payer: wallet.publicKey,
  encrypt: employeeEnc,
});
await sendAndConfirmTransaction(connection, new Transaction().add(view.ix), [wallet, ...view.signers]);
```

The lender reads the copy through Encrypt `ReadCiphertext`, re-encrypted
to their key. That read uses Encrypt's client, not this SDK.

## Other multisigs

* **Clearsig:** `clearsigIntents()` returns the 12 custom-intent templates
  to register on the Clearsig wallet. From the SDK directory:
  `bun examples/clearsig-intents.ts > dagon-payroll-intents.json`.
* **Realms / raw-instruction tools:** `serializeInstructions(ixs)` turns the
  authority instructions into JSON to paste as custom instructions.
  `deserializeInstructions` reverses it.

See [Multisig integration](https://docs.inkwell.finance/dagon/payroll/multisig.md).

## SDK surface

| Export | What it is |
| - | - |
| `PayrollInstructions` | Instruction builders: `createCompany`, `authority.*` (what the multisig signs), `crank.*`, `employee.*`, `grantView`. Builders that create ciphertexts return `{ ix, signers }`. |
| `PayrollPdas` | PDA derivation: `company`, `vault`, `employee`, `run`, `withdrawal`, `encryptCpiAuthority`. |
| `PayrollCrank` | `syncTreasury`, `runPayroll`, `beginTreasuryWithdrawal`, `settleWithdrawal`. |
| `fetchCompany`, `fetchEmployee`, `fetchEmployees`, `fetchPayRun`, `fetchWithdrawal` | Account decoders. Unknown layout versions are rejected. |
| `encryptAmounts`, `deriveEncryptAccounts` | Encrypt helpers. |
| `SquadsV4Authority` | `proposeIxs`, `approveIx`, `executeIx`. |
| `clearsigIntents`, `serializeInstructions`, `deserializeInstructions` | Clearsig templates and raw-instruction serialization. |
| `ViewTarget` | `Treasury`, `Salary`, `Balance`, `PendingBonus`, `RunRemaining`, `RunFlagged`. |
| `DAGON_PAYROLL_PROGRAM_ID`, `ENCRYPT_DEVNET`, `UNLIMITED`, `companyIdFromSlug` | Constants and helpers. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.