@keewallet/waas-sdk
v0.1.14
Published
KeeWallet WaaS SDK (server-side) — create custody wallets, list addresses, run WaaS Transfer / Private Transfer, create non-custodial Private Transfer pay-in orders, create Collect orders, and verify webhooks. HMAC-signed; holds the API secret, server-onl
Maintainers
Readme
@keewallet/waas-sdk
Server-side SDK for KeeWallet Wallet-as-a-Service operations, including custody wallets, addresses, transfers, transfer authorization, private transfers, collection orders, security factors, and webhook verification.
This package holds an API secret. Never import it into browser or mobile code.
Install
npm install @keewallet/waas-sdkNode.js 18 or newer is required.
Create a client
import { createWaasClient } from '@keewallet/waas-sdk'
const waas = createWaasClient({
apiKey: process.env.KEEWALLET_API_KEY,
apiSecret: process.env.KEEWALLET_API_SECRET,
})The default API origin is https://api.keewallet.io. Custom origins must use
HTTPS; plain HTTP is accepted only for localhost development.
Capabilities and addresses
const service = await waas.service.version()
const capabilities = await waas.assets.capabilities({
profile: 'waas',
purpose: 'transfer',
view: 'chains',
})
const addresses = await waas.addresses.list({
page: 1,
pageSize: 50,
chainCode: 'tron',
status: 'active',
})Use the capability response as the authoritative source for supported chains,
assets, and operations. user_wallet_id groups the addresses that belong to one
end-user sub-wallet; transfer methods accept address strings, not internal IDs.
Custody wallets
const created = await waas.wallets.create(
{ walletName: 'Treasury' },
{ idempotencyKey: crypto.randomUUID() },
)A mnemonic may be returned only once. Move it immediately to an approved offline backup process. Never log it, return it to a browser, or store it in analytics.
Transfers
const input = {
chainCode: 'tron',
token: 'USDT',
fromAddress: 'T...',
toAddress: 'T...',
amount: '10',
feeMode: 'platform_paid',
}
const quote = await waas.transfers.quote(input)
const order = await waas.transfers.create(input, {
idempotencyKey: crypto.randomUUID(),
})
const status = await waas.transfers.get(order.order_no)Use platform_paid when KeeWallet Credits cover network fees and sender_paid
when the source address pays them. Quote before requesting user approval.
Direct transfers require a separately granted high-risk scope. Use the transfer authorization workflow when your policy requires maker/checker or end-user factors.
Transfer authorization
const authorization = await waas.transferAuthorizations.create(input, {
idempotencyKey: crypto.randomUUID(),
})
const approved = await waas.transferAuthorizations.approve(
authorization.authorization_id,
{
signingPayload: authorization.signing_payload,
keewalletSignature: authorization.keewallet_signature,
authorization: endUserFactor,
},
{ idempotencyKey: crypto.randomUUID() },
)Return signing payloads unchanged. KeeWallet validates authorization roles, credential binding, replay resistance, limits, and transaction policy.
Private transfer and collection
The main client exposes:
privateTransfersselfCustodyPrivateTransferscollect
Focused subpath clients are also available:
import { createCollectClient } from '@keewallet/waas-sdk/collect'
import { createSelfCustodyPrivateTransferClient } from '@keewallet/waas-sdk/self-custody-private-transfer'Call collect.assets() before creating an order — it returns the usable
chain_code + token pairs and shares its logic with order creation, so anything
it lists can be used. Do not hard-code the result: not every chain takes both
USDT and USDC (Optimism takes USDT only; TRON takes USDT plus native TRX), and
Avalanche and Bitcoin are not supported for collect at all.
collect.listSettlements() (GET /waas/settlements) returns settlement records
(completed settlements only) —
same shape as the collect.settlement webhook. Reconcile orders against
listOrders() and settlements against listSettlements(); they are separate books.
Collect order status is pending → received → settling → settled (or expired),
with settled_at set once it settles.
received and every later status mean the payment succeeded, so ship on received
rather than waiting for settled.
Three webhooks, each answering one question:
collect.payment_received— per order, when it turnsreceived. Carriesreceived_amount(what the customer paid),service_feeandsettle_amount, withsettle_amount + service_fee === received_amount.collect.settlement— one per settlement, when funds reach your main address. Carriessettlement_no,order_nos,pending_amount,settlement_feeandsettled_amount, withpending_amount - settlement_fee === settled_amount.collect.order.expired— per order, when it expires unpaid. A late payment is no longer matched to it automatically. An order gets eithercollect.payment_receivedor this one, never both, so it settles that the order went unpaid; if your own record says paid, treat it as paid and ignore this event rather than rolling the status back.
Acknowledge all three with a 2xx even if you only act on one of them — anything else retries into the dead-letter state.
Fees come at two levels and are charged separately: the order fee is per order (a rate on the amount paid), the settlement fee is charged once per settlement and does not grow with the number of orders. With a 0.5% rate, a settlement fee of 1 and three payments of 100 / 90 / 200, the orders are charged 0.5 / 0.45 / 1 (settling 99.5 / 89.55 / 199) and the settlement moves 388.05 - 1 = 387.05 on-chain.
Collect webhooks are delivered up to 4 times: immediately, then 10s / 20s / 30s — the whole retry chain finishes within a minute. Any 2xx stops it.
Rate limits are counted on two independent axes: 100 requests/minute per API key
and 120 per source IP (api_key_rate_limit_exceeded / ip_rate_limit_exceeded).
Exceeding either returns 429 with Retry-After: 60.
Treat webhooks as the primary channel and polling as a fallback — scan pending
orders no more often than every 30 seconds, and fetch in bulk through the list
endpoints (pageSize up to 100) instead of looping over single-order lookups.
Security factors
Use security.passkeys and security.totp to manage the supported transfer
authorization factors. Factor verification results do not replace transaction
authorization unless the approval endpoint consumes and verifies that factor.
Verify webhooks
Read the raw request body before any JSON middleware changes it:
const verified = waas.verifyWebhook({
headers: request.headers,
rawBody,
})
if (!verified.valid) throw new Error('Invalid KeeWallet webhook')The signature covers the raw body and timestamp. Event identifiers and event types must come from the verified body.
Errors
import { KeeWalletWaasError } from '@keewallet/waas-sdk'
try {
await waas.transfers.get('order_123')
} catch (error) {
if (error instanceof KeeWalletWaasError) {
console.error(error.code, error.status, error.requestId)
}
}Security
- Keep API secrets and custody material in approved backend secret storage.
- Never expose this package through a browser bundle.
- Do not log authorization headers, mnemonics, private keys, TOTP seeds, or passkey assertions.
- Use unique idempotency keys for write operations.
- Verify webhook signatures against the unmodified raw body.
- Grant the minimum API scopes required by each backend service.
License
Proprietary. Contact KeeWallet for licensing terms.
