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

# Quickstart

> Generate Sandbox keys, install @ensc/sdk, list banks, create a crypto-issue on celo-sepolia and verify a webhook.

Five steps take you from an empty project to a completed Sandbox conversion and a verified webhook delivery. Everything runs on your backend: the SDK is server-side only, and every credential it holds is a secret that must never reach a browser bundle.

<Steps>
  <Step title="Generate Sandbox keys">
    Credentials are issued by the dashboard, not the SDK. Sign in at [app.prosperavest.com](https://app.prosperavest.com), pick **Sandbox** and open the **Credentials** tab. Sandbox keys are available once your business profile is complete.

    Click **Generate keys**. The dashboard creates your API key, your encryption key and an Ed25519 signing keypair, registers the public half with ENSC, and shows the six values **once**. Copy them all before closing the dialog; they cannot be shown again.

    ```sh theme={null}
    ENSC_API_KEY=ensc_test_sk_…
    ENSC_MERCHANT_ID=mrc_…
    ENSC_ENCRYPTION_KEY=…            # 43 characters, base64url
    ENSC_ENCRYPTION_KEY_ID=enc_…
    ENSC_SIGNING_KEY_ID=sig_…
    ENSC_SIGNING_PRIVATE_KEY=…       # 43 characters, base64url
    ```

    Store them in your secret manager or environment. The full walkthrough, including key types and rotation, is in [Generating keys](/security/generating-keys).
  </Step>

  <Step title="Install the SDK">
    ```sh theme={null}
    npm install @ensc/sdk
    # the web3 helper (optional) also needs viem:
    npm install @ensc/sdk viem
    ```

    Requires Node 20.19+ or 22.12+ (or any modern runtime with `fetch` and Web Crypto, such as Deno or Bun).

    Construct one client with the six values. All six are required; the client validates their shape at construction and throws `ENSC_VALIDATION_FAILED` with the name of the field that is missing or malformed.

    ```ts theme={null}
    import { EnscClient } from '@ensc/sdk';

    const ensc = new EnscClient({
      apiKey: process.env.ENSC_API_KEY!,
      merchantId: process.env.ENSC_MERCHANT_ID!,
      encryptionKey: process.env.ENSC_ENCRYPTION_KEY!,
      encryptionKeyId: process.env.ENSC_ENCRYPTION_KEY_ID!,
      signingPrivateKey: process.env.ENSC_SIGNING_PRIVATE_KEY!,
      signingKeyId: process.env.ENSC_SIGNING_KEY_ID!,
    });
    ```

    From here on, every write is encrypted and signed automatically, and every successful response is verified and opened for you.
  </Step>

  <Step title="List banks">
    A read is the simplest way to confirm the credentials work. `banks.list()` returns the bank directory for your key's environment; in Sandbox it comes from the sandbox banking network, so no real money is involved.

    ```ts theme={null}
    const { country, banks } = await ensc.banks.list();
    // country === 'NG'; banks is [{ code, name }, ...]
    ```

    Bank codes from this list are what a `fiat-redeem` payout uses later.
  </Step>

  <Step title="Create a crypto-issue on celo-sepolia">
    A `crypto-issue` sends a pair token (`USDC`, `USDT` or `CELO`) and receives ENSC. Sandbox conversions run on `celo-sepolia`, so the wallet you name must hold the test pair token on that chain and enough testnet CELO for gas.

    ENSC never holds a wallet key and never broadcasts. `create` returns a signed **voucher** with the calldata your wallet must sign: an optional `approvalTransaction` and then `transaction`. The optional web3 helper sends both and returns the hash; you then report the hash back so ENSC can verify the receipt.

    ```ts theme={null}
    import { executeVoucher } from '@ensc/sdk/web3';

    const c = await ensc.conversions.create({
      type: 'crypto-issue',
      chain: 'celo-sepolia',
      wallet,                 // the wallet that will sign; the voucher binds to it
      pair: 'USDC',
      amount: '100',
    });
    // c.status === 'voucher_issued'; c.voucher.transaction is { from, to, data, value: '0', chainId }

    // Sign with the wallet's signer. executeVoucher sends the approval (if any), then the converter call.
    const { transaction } = await executeVoucher(c.voucher!, signer, { rpcUrl });
    const settled = await ensc.conversions.events.confirmed(c.reference, transaction.txHash);
    // settled.status === 'succeeded'
    ```

    `signer` is a raw wallet private key or any viem account (`privateKeyToAccount`, `mnemonicToAccount`, `toAccount` around an HSM, KMS or custody signer, or the JSON-RPC account of a wallet a user connected). It is a **separate** secret from the ENSC credentials, never touches the ENSC API and is never stored by the SDK; pass it per call. Any EVM signer works with the calldata, and nothing about ENSC requires exporting a private key; see [Conversions](/guides/conversions).
  </Step>

  <Step title="Verify a webhook">
    ENSC tells your backend what happened by sending signed events to an https URL you own. Register an endpoint (from the dashboard's Webhooks tab or with the SDK), then write a handler that verifies the signature over the raw body, answers `200`, and processes afterwards.

    ```ts theme={null}
    const endpoint = await ensc.webhookEndpoints.create({
      env: 'test',
      url: webhookUrl,        // a public https URL on your backend
      eventTypes: ['*'],
    });
    ```

    ```ts theme={null}
    import { EnscClient } from '@ensc/sdk';

    // ENSC's webhook keys as { [kid]: publicKey }. Load once and cache.
    let enscKeys = await EnscClient.fetchPublicKeys();

    export async function handleEnscWebhook(rawBody: string, headers: Headers): Promise<Response> {
      const kid = headers.get('X-ENSC-Key-Id');
      if (kid && !(kid in enscKeys)) enscKeys = await EnscClient.fetchPublicKeys(); // unknown kid: refetch
      let event;
      try {
        event = EnscClient.constructEvent({ body: rawBody, headers, publicKey: enscKeys });
      } catch {
        return new Response('invalid signature', { status: 401 });
      }
      if (await alreadySeen(event.id)) return new Response(null, { status: 200 });
      await enqueue(event);                 // process after answering
      return new Response(null, { status: 200 });
    }
    ```

    `publicKey` is the key map `EnscClient.fetchPublicKeys()` loads from `GET /v1/.well-known/ensc-public-keys.json`; the verifier picks the key named by `X-ENSC-Key-Id`, so a key rotation needs no redeploy. There is no shared secret to store or rotate.

    Queue a signed test event and read the attempt back. `sendTest` queues one event in the endpoint's environment; every active endpoint there that subscribes to the type receives it, and `willDeliverToTargetEndpoint` says whether the endpoint you named is among them.

    ```ts theme={null}
    const { eventId, willDeliverToTargetEndpoint } = await ensc.webhookEndpoints.sendTest(endpoint.id, {
      eventType: 'conversion.succeeded',
    });
    const detail = await ensc.events.get(eventId);
    // detail lists each delivery attempt with the HTTP status your endpoint answered
    ```

    Retries, ordering, de-duplication and the event catalogue are in [Webhooks](/guides/webhooks).
  </Step>
</Steps>

## Next

* [Environments](/environments): Sandbox versus Live, chains and the sandbox bank rail.
* [Going live](/going-live): the checklist for Live keys, the IP allowlist and the `celo` chain.
* [SDKs](/sdks): the full `@ensc/sdk` surface.
