> ## 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.

# Power Yield for Stellar Balances

> Use Ground Portfolio Wallets to offer yield on customer USDC held on Stellar. Ground provides the wallet, allocation, bridging, and settlement infrastructure while your application owns the customer experience.

## Configure your backend

Create an API key in [Ground Portal → API Keys](https://portal.groundtech.co/api-keys).
Copy it when shown and store it in your backend's secret manager as
`GROUND_API_TOKEN`. Never expose an API key in browser or mobile code. The
examples use sandbox:

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

For production, use `https://production.groundtech.co`, a production API key, and `stellar` as the chain key.

## 1. Create a wallet for the customer

Create one Portfolio Wallet for each customer. Store the Ground wallet ID on
the customer record in your database.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type Wallet = {
  id: string;
  status: "creating" | "idle" | "failed";
  depositAddresses: Record<string, string>;
  balance: {
    totalUsd: string;
    withdrawableUsd: string;
    earnedUsd: string;
  };
  positions: Array<{
    id: string;
    kind: string;
    label: string;
    valueUsd: string;
  }>;
};

async function createCustomerWallet(customerId: string) {
  const requestId = crypto.randomUUID();
  await saveGroundRequestId(customerId, "wallet_creation", requestId);

  const response = await fetch(`${baseUrl}/v2/wallets`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiToken}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      requestId,
      label: `Customer ${customerId}`,
      autoRebalance: true,
      strategy: {
        allocations: {
          usdc: [
            {
              yieldSourceId: "your-usdc-yield-source-id",
              pct: 100,
            },
          ],
        },
      },
    }),
  });

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

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

async function getCustomerWallet(walletId: string) {
  const response = await fetch(`${baseUrl}/v2/wallets/${walletId}`, {
    headers: { Authorization: `Bearer ${apiToken}` },
  });

  if (!response.ok) throw new Error(await response.text());
  return (await response.json()) as Wallet;
}

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

Replace `your-usdc-yield-source-id` with the USDC yield source configured for
your integration. Save the `requestId` before sending the request. If the
request times out, retry it with the same ID.

Wallet creation is asynchronous. Subscribe to the
`portfolio_wallet.status_changed` webhook before creating the wallet. Store the
returned wallet ID, then wait for its status to become `idle`. If a webhook is
delayed or missed, reconcile the state with `GET /v2/wallets/{id}`. Treat
`failed` as a provisioning failure.

Stellar setup happens automatically in the background. An `idle` wallet may
still be preparing its Stellar address. Poll `GET /v2/wallets/{id}` until
`depositAddresses[stellarChain]` appears before depositing. Other returned
addresses remain usable during setup.

If your organization controls its Turnkey signing policy, a new Stellar policy
must be approved in the portal before Stellar setup can finish. Ground does not
change that policy during wallet setup. If the address remains absent, check the
portal's Approvals tab or contact Ground about the required policy release.

For an older wallet without a Stellar deposit address, contact Ground to arrange
setup.

## 2. Deposit USDC and start earning

Get the customer's Stellar deposit address from the wallet:

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
const wallet = await getCustomerWallet(customerWallet.id);
const depositAddress = wallet.depositAddresses[stellarChain];

if (!depositAddress) {
  throw new Error("Stellar deposit address is not ready");
}
```

Display this address to the customer, or use it as the destination in your
platform's Stellar transfer flow. Send USDC over the selected Stellar network
to this address. The onchain transfer creates the deposit, so there is no
separate Ground deposit API request.

Subscribe to `portfolio_wallet.deposit.status_changed` before initiating the
transfer. When the deposit becomes `completed`, refresh the wallet and update
the customer-facing balance.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
type DepositWebhook = {
  event: "portfolio_wallet.deposit.status_changed";
  deposit: {
    id: string;
    walletId: string;
    amount: string;
    chain: string;
    status: "processing" | "completed" | "failed";
  };
};

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

  const customer = await findCustomerByGroundWalletId(
    event.deposit.walletId,
  );
  const wallet = await getCustomerWallet(event.deposit.walletId);

  await updateCustomerPortfolio(customer.id, wallet);
}
```

After the deposit completes, the USDC appears as wallet cash. Automatic
rebalancing moves eligible funds toward the configured strategy. Ground handles
bridging automatically when a yield source is on another supported network,
without a separate customer approval.

## 3. Display balances and earnings

Use the customer's wallet ID to fetch their current balance and yield details:

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

async function getCustomerYield(walletId: string) {
  const response = await fetch(
    `${baseUrl}/v2/wallets/${walletId}/yield`,
    { headers: { Authorization: `Bearer ${apiToken}` } },
  );

  if (!response.ok) throw new Error(await response.text());
  return (await response.json()) as WalletYield;
}

const [currentWallet, customerYield] = await Promise.all([
  getCustomerWallet(customerWallet.id),
  getCustomerYield(customerWallet.id),
]);
```

Use these values in your customer experience:

| Value | Display |
| - | - |
| `currentWallet.balance.totalUsd` | Total portfolio balance |
| `currentWallet.balance.withdrawableUsd` | Amount currently available to withdraw |
| `customerYield.earnedUsd` | Lifetime yield earned |
| `customerYield.positions` | Yield sources, APY, and deployed value |

Dollar values are returned as decimal strings. Refresh the wallet after deposit
and withdrawal updates. You can also refresh it periodically while the customer
is viewing their portfolio.

## 4. Withdraw USDC to Stellar

Configure [transaction approvals](https://docs.groundtech.co/docs/portfolio-wallets/transaction-approvals.md)
for external payouts, which require customer approval.

First, preview the USDC withdrawal amount and Stellar destination chain. If the
preview looks correct, create the withdrawal with the customer's destination
address and a new `requestId`. Save the returned withdrawal ID in your database.

```ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
async function requestStellarWithdrawal(input: {
  customerId: string;
  walletId: string;
  destinationAddress: string;
  amountUsd: string;
}) {
  const withdrawalRequest = {
    amountUsd: input.amountUsd,
    token: "usdc",
    destinationChain: stellarChain,
  };

  const previewResponse = await fetch(
    `${baseUrl}/v2/wallets/${input.walletId}/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 requestId = crypto.randomUUID();
  await saveGroundRequestId(input.customerId, "withdrawal", requestId);

  const withdrawalResponse = await fetch(
    `${baseUrl}/v2/wallets/${input.walletId}/withdrawals`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ...withdrawalRequest,
        requestId,
        destinationAddress: input.destinationAddress,
      }),
    },
  );
  if (!withdrawalResponse.ok) {
    throw new Error(await withdrawalResponse.text());
  }

  const withdrawal = await withdrawalResponse.json();
  await saveGroundWithdrawalId(input.customerId, withdrawal.id);

  return { preview, withdrawal };
}
```

The destination Stellar account must be active and configured to receive Circle
USDC on the selected network.

External payouts require Ground + customer approval. Process those activities
through your organization signing flow, then track
`portfolio_wallet.withdrawal.status_changed` until the withdrawal is
`completed` or `failed`.


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