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

# Example: Neobank/Exchanges

Set `autoRebalance: false` when creating the wallet so your application controls
exact dollar allocations and deposits remain in cash until you allocate them.

```mermaid theme={"theme":{"light":"github-light","dark":"github-dark"}}
sequenceDiagram
    participant Customer
    participant App as "Your application"
    participant Ground

    App->>Ground: Create wallet without auto-rebalance
    Ground-->>App: Wallet and deposit addresses
    Customer->>Ground: Send stablecoin
    Ground-->>App: Deposit completed
    App->>Ground: Preview allocation
    App->>Ground: Create allocation
    Ground-->>App: Rebalance completed
    App->>Ground: Read customer positions and yield
    Customer->>App: Request withdrawal
    App->>Ground: Preview and create withdrawal
    Ground-->>Customer: Stablecoin payout
```

Store your API key in `GROUND_API_TOKEN`. The examples below use the sandbox API:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const baseUrl = "https://sandbox.groundtech.co";
const apiToken = process.env.GROUND_API_TOKEN!;
```

## 1. Create a wallet for each customer

Create a wallet without auto-rebalancing by setting `autoRebalance: false`. Save the Ground wallet
ID on your customer record.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type Wallet = {
  id: string;
  autoRebalance: boolean;
  status: string;
  depositAddresses: Record<string, string>;
  positions: Array<{
    id: string;
    kind: string;
    label: string;
    valueUsd: string;
  }>;
};

async function createCustomerWallet(customerId: string) {
  const response = await fetch(`${baseUrl}/v2/wallets`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requestId: crypto.randomUUID(),
      label: `Customer ${customerId}`,
      autoRebalance: false,
    }),
  });

  if (!response.ok) throw new Error(await response.text());
  const wallet = (await response.json()) as Wallet;

  await saveGroundWalletId(customerId, wallet.id);
  return wallet;
}

const customerWallet = await createCustomerWallet("customer-1234");
```

Subscribe to `portfolio_wallet.status_changed` before creating the wallet. When
the matching `walletId` reaches `idle`, read `GET /v2/wallets/{id}` and show the
customer the appropriate deposit address. Handle `failed` as a provisioning
failure, and use the wallet read to reconcile a delayed or missed webhook.

## 2. Receive the completed deposit

Subscribe to `portfolio_wallet.deposit.status_changed` or poll the wallet's
deposits. Allocate funds only after the deposit becomes `completed`. Verify the
webhook signature before parsing it, and deduplicate deliveries with the
`Ground-Event-Id` header.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type CompletedDeposit = {
  id: string;
  walletId: string;
  amount: string;
  status: "completed";
  cashPositionId: string;
};

type DepositWebhook = {
  event: "portfolio_wallet.deposit.status_changed";
  deposit: {
    id: string;
    walletId: string;
    amount: string;
    status: "processing" | "completed" | "failed";
    cashPositionId: string | null;
  };
};

async function handleDepositEvent(event: DepositWebhook) {
  if (event.deposit.status !== "completed") return;
  if (!event.deposit.cashPositionId) return;

  await allocateDeposit(
    event.deposit as CompletedDeposit,
    "morpho-august-usdc-v2",
  );
}
```

Use `cashPositionId` as the source when allocating this deposit. IDs are unique
to each environment, so do not reuse sandbox IDs in production.

See [Webhook signature verification](/docs/portfolio-wallets/webhook-signature-verification)
for the complete verification flow. Use the wallet read as a fallback when a
webhook is delayed or missed.

## 3. Allocate the cash

Fetch `GET /v2/wallets/yield-sources` and use a returned source `id` as the
destination. Send the same `allocations` array to preview and create.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function allocateDeposit(
  deposit: CompletedDeposit,
  yieldSourceId: string,
) {
  const allocations = [
    {
      from: deposit.cashPositionId,
      to: yieldSourceId,
      amountUsd: deposit.amount,
    },
  ];

  const previewResponse = await fetch(
    `${baseUrl}/v2/wallets/${deposit.walletId}/allocate-preview`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ allocations }),
    },
  );
  if (!previewResponse.ok) throw new Error(await previewResponse.text());
  const preview = await previewResponse.json();

  const allocationResponse = await fetch(
    `${baseUrl}/v2/wallets/${deposit.walletId}/allocate`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        requestId: crypto.randomUUID(),
        allocations,
      }),
    },
  );
  if (!allocationResponse.ok) throw new Error(await allocationResponse.text());
  const allocation = await allocationResponse.json();

  return { preview, allocation };
}
```

The response includes the `rebalanceId`, `requestId`, `createdAt`, current
`status`, and accepted `allocations`. Returned dollar amounts use six decimal
places. Track `portfolio_wallet.rebalance.status_changed` or rebalance activity
until it finishes.

If Ground returns `workflow_conflict`, wait for the active withdrawal or
rebalance to finish, then retry.

## 4. Read or change an allocation

Read the wallet to get the customer's current cash and investments:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const response = await fetch(`${baseUrl}/v2/wallets/${customerWallet.id}`, {
  headers: { Authorization: `Bearer ${apiToken}` },
});

if (!response.ok) throw new Error(await response.text());
const wallet = (await response.json()) as Wallet;
console.log(wallet.positions);
```

To move an existing investment, use IDs from `wallet.positions`:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const allocations = [
  {
    from: "morpho-august-usdc-v2",
    to: "syrup-usdc",
    amountUsd: "250.000000",
  },
];

const previewResponse = await fetch(
  `${baseUrl}/v2/wallets/${customerWallet.id}/allocate-preview`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ allocations }),
  },
);

if (!previewResponse.ok) throw new Error(await previewResponse.text());

const allocationResponse = await fetch(
  `${baseUrl}/v2/wallets/${customerWallet.id}/allocate`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requestId: crypto.randomUUID(),
      allocations,
    }),
  },
);

if (!allocationResponse.ok) {
  throw new Error(await allocationResponse.text());
}
```

## 5. View yield

Read the customer's lifetime earnings, estimated annualized yield, and current
breakdown by yield source:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type WalletYield = {
  walletId: string;
  earnedUsd: string;
  annualizedUsd: string;
  currentBalanceUsd: string;
  positions: Array<{
    yieldSourceId: string;
    name: string;
    apyBps: number | null;
    deployedValueUsd: string;
  }>;
};

const response = await fetch(
  `${baseUrl}/v2/wallets/${customerWallet.id}/yield`,
  { headers: { Authorization: `Bearer ${apiToken}` } },
);

if (!response.ok) throw new Error(await response.text());
const customerYield = (await response.json()) as WalletYield;

console.log({
  earnedUsd: customerYield.earnedUsd,
  annualizedUsd: customerYield.annualizedUsd,
  positions: customerYield.positions,
});
```

This response can be shown directly in the customer-facing application. See
[Calculating Yield Accrual](/docs/portfolio-wallets/calculating-yield-accrual)
for display and calculation guidance.

## 6. Withdraw funds

Omit `sources` to let Ground choose the positions. To withdraw from specific
positions, include the amount from each source. Source amounts must add up to
`amountUsd`, and each source must contribute at least \$0.01.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const withdrawalRequest = {
  amountUsd: 1_000,
  token: "usdc",
  destinationChain: "ethereum_sepolia",
  sources: [
    { id: "morpho-august-usdc-v2", amountUsd: 600 },
    { id: "syrup-usdc", amountUsd: 400 },
  ],
};

const previewResponse = await fetch(
  `${baseUrl}/v2/wallets/${customerWallet.id}/withdrawal-preview`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(withdrawalRequest),
  },
);

if (!previewResponse.ok) throw new Error(await previewResponse.text());
const preview = await previewResponse.json();

const withdrawalResponse = await fetch(
  `${baseUrl}/v2/wallets/${customerWallet.id}/withdrawals`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      ...withdrawalRequest,
      requestId: crypto.randomUUID(),
      destinationAddress: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
    }),
  },
);

if (!withdrawalResponse.ok) throw new Error(await withdrawalResponse.text());
const withdrawal = await withdrawalResponse.json();

console.log({ preview, withdrawal });
```

Send the same source amounts to preview and create. Track
`portfolio_wallet.withdrawal.status_changed` until the withdrawal finishes.

## Test the integration

In sandbox:

1. Create a wallet without automatic rebalancing and save its ID on a test customer.
2. Deposit test USDC and confirm the completed deposit includes `cashPositionId`.
3. Allocate part of the cash to two yield sources.
4. Confirm the rebalance finishes and the wallet returns the new positions.
5. Confirm `GET /v2/wallets/{id}/yield` returns the customer's yield summary.
6. Move funds between two existing positions.
7. Complete one automatic withdrawal and one selected-source withdrawal.

Save each `requestId` before sending a request. If the request times out, retry
with the same ID and body.
