# Using Signer JS Library (Low-Level TypeScript)

This library is suitable if you need more control over the integration process, are not using React, or want to build a custom UI. It directly implements the client side of the ICRC wallet standards.

{% hint style="info" icon="money-check-pen" %}
Please refer to the repo for latest infos: [**https://www.npmjs.com/package/@icp-sdk/signer**](https://www.npmjs.com/package/@icp-sdk/signer)
{% endhint %}

**Install:**\
You'll need the core library, the web transport for browser-based wallets like OISY, and potentially the agent helper.

```bash
npm install @icp-sdk/signer @dfinity/principal @dfinity/ledger-icrc
# or yarn add ...
# or pnpm add ...
```

* `@icp-sdk/signer` — `Signer` for standardized signer interaction
* `@icp-sdk/signer/agent` — `SignerAgent` as a drop-in replacement for `HttpAgent`
* `@icp-sdk/signer/web` — `PostMessageTransport` for web-based signers
* `@icp-sdk/signer/extension` — `BrowserExtensionTransport` for browser extension signers
* `@dfinity/ledger-icrc`: Helpers for interacting with ICRC-1 ledgers (like the ICP ledger, though it's technically not ICRC-1 but similar for transfers).

**Connect to OISY (Example):**\
You need to trigger this connection process from a user action, like clicking a "Connect OISY" button.

```ts
import { Signer } from '@icp-sdk/signer';
import { PostMessageTransport, type PostMessageTransportOptions } from '@icp-sdk/signer/web';
import { SignerAgent } from '@icp-sdk/signer/agent';
import { IcrcLedgerCanister, type Account } from '@dfinity/ledger-icrc'; // Using ICRC-1 types for accounts
import { Principal } from '@dfinity/principal';
import { Actor, HttpAgent } from '@dfinity/agent'; // Actor is needed, HttpAgent might be for other non-signed calls

// Ensure you have the IDL for the ICP ledger if not using a higher-level library for it
// For this example, we'll use IcrcLedgerCanister from @dfinity/ledger-icrc which has its own IDL.

let signerInstance: Signer | null = null;
let transportInstance: PostMessageTransport | null = null;
let walletAgent: SignerAgent | null = null; // This will be the agent that uses the wallet
let userPrincipal: Principal | null = null;
let userAccountsFromWallet: Account[] | null = null; // Using ICRC-1 Account type

// OISY's official signing endpoint
const OISY_SIGN_URL = 'https://oisy.com/sign';
const ICP_LEDGER_CANISTER_ID = 'ryjl3-tyaaa-aaaaa-aaaba-cai';

async function connectOisyWithSignerJs() {
  console.log('Attempting to connect OISY with signer-js...');

  // 1. Configure and create the transport for OISY (uses ICRC-29 postMessage)
  const transportOptions: PostMessageTransportOptions = {
    targetUrl: OISY_SIGN_URL,
    // Configure window features for the popup
    windowFeatures: 'width=500,height=700,noopener,noreferrer',
  };
  transportInstance = new PostMessageTransport(transportOptions);

  // 2. Create the Signer instance with the transport
  signerInstance = new Signer({ transport: transportInstance });

  try {
    // 3. Initiate the connection (this opens the OISY window for user approval)
    // This step handles the ICRC-25 handshake.
    await transportInstance.connect();
    console.log('OISY transport connected successfully!');

    // 4. Get user accounts (uses ICRC-27)
    // This retrieves the principal and associated accounts the user has permitted.
    const accountsResult = await signerInstance.accounts();
    if (!accountsResult || accountsResult.length === 0) {
      throw new Error('No accounts found or permission denied by user.');
    }
    userAccountsFromWallet = accountsResult.map((acc) => ({
      owner: acc.owner,
      subaccount: acc.subaccount,
    }));
    userPrincipal = userAccountsFromWallet.owner; // Typically use the principal from the first account
    console.log('OISY Accounts:', userAccountsFromWallet);
    console.log('User Principal:', userPrincipal.toText());

    // 5. Create a SignerAgent (optional but highly convenient)
    // This agent will use our 'signerInstance' to sign any outgoing update calls.
    // For query calls, it can use a standard anonymous HttpAgent or be configured.
    walletAgent = await SignerAgent.create({
      signer: signerInstance,
      identity: userPrincipal, // The identity obtained from the wallet
      host: 'https://icp-api.io', // Mainnet IC boundary node URL
    });
    console.log('SignerAgent created successfully.');

    // Update your UI to reflect the connected state
    updateUIOnConnect(userPrincipal.toText(), userAccountsFromWallet); // Implement this function
  } catch (error) {
    console.error('OISY Connection failed:', error);
    alert(`OISY Connection Error: ${error.message || error}`);
    await disconnectOisyWithSignerJs(); // Clean up any partial connection
  }
}

async function disconnectOisyWithSignerJs() {
  if (transportInstance && transportInstance.connected) {
    await transportInstance.disconnect();
  }
  signerInstance = null;
  transportInstance = null;
  walletAgent = null;
  userPrincipal = null;
  userAccountsFromWallet = null;
  updateUIOnDisconnect(); // Implement this function
  console.log('Disconnected from OISY.');
}

// --- Placeholder UI update functions (you need to implement these) ---
function updateUIOnConnect(principalText: string, account: Account) {
  document.getElementById('connect-oisy-signerjs').style.display = 'none';
  document.getElementById('disconnect-oisy-signerjs').style.display = 'block';
  document.getElementById('user-info').innerText =
    `Connected: ${principalText}, Account: ${JSON.stringify(account)}`;
  document.getElementById('transfer-icp-signerjs').style.display = 'block';
}
function updateUIOnDisconnect() {
  document.getElementById('connect-oisy-signerjs').style.display = 'block';
  document.getElementById('disconnect-oisy-signerjs').style.display = 'none';
  document.getElementById('user-info').innerText = 'Not Connected';
  document.getElementById('transfer-icp-signerjs').style.display = 'none';
}

// --- Add button listeners in your HTML ---
// <button id="connect-oisy-signerjs">Connect OISY (signer-js)</button>
// <button id="disconnect-oisy-signerjs" style="display:none;">Disconnect</button>
// <p id="user-info">Not Connected</p>
// <button id="transfer-icp-signerjs" style="display:none;">Send ICP (signer-js)</button>
document
  .getElementById('connect-oisy-signerjs')
  ?.addEventListener('click', connectOisyWithSignerJs);
document
  .getElementById('disconnect-oisy-signerjs')
  ?.addEventListener('click', disconnectOisyWithSignerJs);
```

**Making Calls (Example with SignerAgent):**\
Use the `walletAgent` (which is a `SignerAgent`) to create actors and call canister methods.

```ts
// (Continuing from the previous signer-js example)

async function transferIcpWithSignerJs() {
  if (!walletAgent || !userAccountsFromWallet || userAccountsFromWallet.length === 0) {
    alert('Not connected or no accounts available!');
    return;
  }

  // Example recipient (ensure this is a valid account structure for the ledger)
  const recipientPrincipal = Principal.fromText('uzr34-vyaaa-aaaaq-aaaea-cai'); // Replace
  const recipientAccount: Account = { owner: recipientPrincipal, subaccount: [] }; // ICRC-1 style account
  const amount = 500_000n; // 0.005 ICP (500,000 e8s)

  try {
    // Use IcrcLedgerCanister helper for ICRC-1 compliant ledgers.
    // Note: The main ICP ledger is not strictly ICRC-1 but has a similar transfer method.
    // For the actual ICP ledger, you might need its specific IDL and AccountIdentifier type.
    // This example assumes an ICRC-1 ledger for simplicity with @dfinity/ledger-icrc.
    // If targeting ICP ledger, you'd use its specific `transfer` args.

    // Let's assume we are interacting with an ICRC-1 ledger canister
    const icrcLedger = IcrcLedgerCanister.create({
      agent: walletAgent, // The agent that will use OISY for signing
      canisterId: Principal.fromText('mxzaz-hqaaa-aaaar-qaada-cai'), // Example ICRC-1 ledger ID (ckBTC)
    });

    console.log('Requesting ICRC-1 transfer via signer-js agent to OISY...');
    // This call will be sent to OISY for signing via the SignerAgent -> Signer -> Transport chain.
    // OISY will show an ICRC-21 consent message.
    const blockIndex = await icrcLedger.transfer({
      to: recipientAccount,
      amount: amount,
      // fee, memo, from_subaccount, created_at_time are optional for ICRC-1 transfer
      // and might be automatically filled or handled by OISY/ledger defaults.
    });

    console.log(`ICRC-1 Transfer OK! Block index: ${blockIndex}`);
    alert(`ICRC-1 Transfer successful! Block index: ${blockIndex}`);
  } catch (error) {
    console.error('ICRC-1 Transfer failed:', error);
    alert(`ICRC-1 Transfer failed: ${error.message || error}`);
  }
}

// Add listener to a transfer button
document
  .getElementById('transfer-icp-signerjs')
  ?.addEventListener('click', transferIcpWithSignerJs);
```

When `icrcLedger.transfer` (or any other update call via an actor using `walletAgent`) is executed, the `SignerAgent` directs the signing request to OISY. OISY then presents the user with a consent popup (formatted according to ICRC-21, if the wallet supports it well). If the user approves, OISY signs the transaction, and it's dispatched to the IC.<br>

### Signer JS Summary

This approach involves more manual setup for UI and state management but offers high flexibility and direct use of ICRC standards. It's ideal for non-React projects, custom UIs, or when you need a deep, standards-compliant integration that could work with various wallets supporting ICRC-25/27/29/49.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.oisy.com/for-developers/using-oisy-wallet-in-your-icp-dapp/using-signer-js-library-low-level-typescript.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
