Link a US bank account using Plaid, then debit it on demand via Brale ACH. The proceeds land as DUSD on Kusama Asset Hub and are teleported straight to the Kreivo ledger account associated with the bank URN — one call, one swap order, no escrow hop.
Pass returnUrl on create() (the SDK sends it in the medium input body). The server mints a short-lived session token, builds a hosted URL, and returns it as details.linkUrl (and may include details.jwt).
start-link.ts
const pending = await user.accounts.externalUsBank.create({ label: 'My bank', ledgerId: pocket.ledgerId, returnUrl: 'https://app.example.com/wallet/plaid-return', state: 'user-session-xyz', // optional opaque correlator});if (!pending.details.linkUrl) { throw new Error('Expected linkUrl — check returnUrl origin allowlist');}// Send the user to the hosted page (redirect, email, deep link…)redirectTo(pending.details.linkUrl);
The hosted page handles Plaid Link, exchanges the public_token, then redirects to:
returnUrl origin must be allowlisted
The server validates the origin of returnUrl against PLAID_LINK_RETURN_URL_ALLOWLIST. If it isn't allowlisted, the create call fails before any link token is minted.
Omit returnUrl. Use details.linkToken to run Plaid Link inside your own frontend, then call exchangePublicToken() from your backend.
start-link.ts
const pending = await user.accounts.externalUsBank.create({ label: 'My bank', ledgerId: pocket.ledgerId, // optional: attach to an existing ledger pool});if (!pending.details.linkToken) { throw new Error('Expected Plaid linkToken');}// pending.details.linkToken → pass to Plaid Link on the frontend// pending.details.linkTokenExpiration → ISO 8601 expiry
Run Plaid Link with the linkToken. Plaid returns a public_token when the user completes the flow. Exchange it:
Once linkStatus === 'active', call pull() to debit the bank via Brale ACH and swap the proceeds to DUSD on Kusama. The DUSD is teleported directly to the Kreivo ledger account associated with the bank URN — no intermediate escrow.
pull.ts
const order = await user.accounts.externalUsBank.pull({ urn: linked.urn, // active external-us-bank account URN amount: '100.00', // USD as a decimal STRING — never a number});console.log(order.orderSig); // "0x…" — correlate webhooks (swap.order.*)console.log(order.status); // "pending"console.log(order.graphId); // instruction graph executing the swap
interface PullExternalUsBankResult { orderSig?: string; // stable handle for the swap order — use for webhook correlation graphId?: string; // instruction graph executing the swap status?: string; // initial swap status ("pending", "running", …) execution?: unknown; // raw swap.take execution payload (opaque) requestId?: string; // mediums service request id (for support tickets)}
The pull endpoint returns immediately after swap.take auto-executes the first node of the instruction graph (which resolves the Brale account/address). The actual ACH settlement and Kusama teleport happen asynchronously. Two ways to observe progress:
Webhooks — subscribe to swap.order.* and transfer.* events; match on order.orderSig.
Polling — query the swap service for the order by orderSig.