Skip to main content

Step 1: Get DS Tokens

The first step is funding the investor wallet with testnet DS Token (DST) — minted from the project's faucet, which also auto-registers the wallet in the DS registry.

DS Token via the faucet

What actually happens on-chain

The faucet does not mint. Faucet.requestTokens(recipient) (in bc-securitize-faucet-sc):

  1. Checks getRemainingCooldown(recipient) == 0.
  2. Checks the provider wallet has enough allowance: IERC20(token).allowance(providerWallet, address(faucet)) >= dripAmount.
  3. If registry.getInvestor(recipient) is empty, calls registry.addWallet(recipient, newInvestorId) and emits WalletAutoRegistered(recipient, investorId).
  4. Calls IERC20(token).transferFrom(providerWallet, recipient, dripAmount).
  5. Emits TokensRequested(recipient, dripAmount) and updates lastClaimTime[recipient].

The auto-registration in step 3 is the reason the next steps work — VaultRegistrar.registerVault() will fail with InvestorNotFound for any wallet that has not been seen by the registry.

Path A — Frontend UI

Visit /faucet in the sandbox frontend.

  1. Pick a network from the dropdown (default: ethereum-sepolia).
  2. Enter the investor wallet address (or auto-fill from the connected wallet).
  3. Click Request tokens.
  4. The page shows the tx hash linked to the appropriate block explorer.

The frontend posts to POST /api/v1/drip on bc-labs-gw. The gateway holds FAUCET_OPERATOR_KEY and submits the on-chain requestTokens(investor) for you.

Path B — Direct gateway call

curl -X POST "$LABS_GW_URL/api/v1/drip" \
-H "Content-Type: application/json" \
-d '{
"networkId": "ethereum-sepolia",
"recipient": "0xYourInvestorWallet"
}'

Successful response:

{ "success": true, "txHash": "0x…", "networkId": "ethereum-sepolia" }

Important: the gateway returns on submit, not on confirmation. Watch the tx hash on the block explorer if you need to wait for finality. See the gateway caveat below.

Path C — Direct on-chain (admin/operator only)

If you hold a faucet OPERATOR_ROLE key, call requestTokens(recipient) directly. Most external developers will not have this — use Path A or B instead. There is also requestTokensWithSignature(recipient, nonce, deadline, signature) for delegated submission, with a per-signer nonce tracked via OZ NoncesUpgradeable.

Cooldown, drip amount, and limits

PropertyDefault in test fixtureHow to read at runtime
dripAmount100 * 10^6 (100 DST, 6 decimals)faucet.dripAmount()
cooldownPeriod86_400 seconds (24h)faucet.cooldownPeriod()
Per-recipient cooldowntracked per walletfaucet.getRemainingCooldown(recipient)
Provider allowance leftdrives InsufficientAllowancefaucet.getAvailableAllowance()
Per-IP rate limit (gateway)10 / 24henforced in bc-labs-gw RateLimitGuard
Per-wallet rate limit (gateway)3 / 24henforced in bc-labs-gw RateLimitGuard
Strike threshold (gateway)3 → 24h IP blockenforced in bc-labs-gw RateLimitGuard

The contract-level cooldown and the gateway-level rate limit are independent. Hitting either one will block a request.

Supported networks

NetworkChain IDFaucet env var (gateway)Faucet env var (frontend)
Ethereum Sepolia11155111FAUCET_SEPOLIAVITE_FAUCET_SEPOLIA

If the Sepolia env vars are unset on the gateway, the faucet flow will not initialize correctly. Watch the gateway boot logs to confirm the expected chain initialized.

Errors and how to recover

WhereErrorCauseFix
ContractCooldownNotElapsed(address,uint256)Recipient claimed within cooldownPeriodWait until getRemainingCooldown(recipient) == 0
ContractInsufficientAllowance(uint256,uint256)Provider wallet allowance to faucet too lowOperator must top up IERC20.approve(faucet, …) from the provider wallet
ContractInvalidAddress()Recipient is the zero addressPass a valid address
Gateway403 ForbiddenIP blocklistedContact support if the IP is auto-blocked
Gateway429 Too Many Requests (with Retry-After)Per-IP or per-wallet rate limit hitWait Retry-After seconds, then retry
Gateway400 Bad RequestnetworkId not configured on the gateway, or the contract reverted with one of the aboveCheck gateway logs and the supported-networks list

Gateway caveats

The gateway is a serial-submit relay, not a confirmation service. Two consequences:

  1. Returns on submit, not on inclusion. The 200 response only proves the tx was broadcast. Confirm by polling the block explorer or eth_getTransactionReceipt.
  2. Single-file queue per service. Drip requests are serialized through one promise chain. A stuck tx blocks the next drip until it resolves or times out.

Operator checklist

After step 1 you should be able to verify, on the same chain you'll use throughout the integration:

  • IERC20(dst).balanceOf(investor) > 0
  • IDSToken(dst).getDSService(4) returns a non-zero address (the registry service)
  • IDSRegistryService(registry).getInvestor(investor) returns a non-empty string

Next step

Step 2: Register Operator