{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Run A Workflow Across Custodians","siteUrl":"https://opensource.ripple.com/","meta":[{"name":"google-site-verification","content":"bLwyBi1imklcIuQxZ7JeI_kRF5Mg7yfr6arpEQV2nsE"}],"llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]},"description":"Drive accounts held by different custodians from a single client, routing each operation to the connector that owns the account."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"run-a-workflow-across-custodians","__idx":0},"children":["Run A Workflow Across Custodians"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A single client can drive accounts held by different connectors. Each vertical operation routes to the connector that owns the account it acts on — named per call with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["from"]},", or the primary signer by default — so a workflow spanning custodians is just an ordered sequence of ordinary operation calls."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"/**\n * Run a workflow across two custodians.\n *\n * A single client can drive accounts held by different connectors: each vertical\n * operation routes automatically to the custodian that owns the account it acts on —\n * named via `from`, or the primary signer by default.\n */\nimport { PalisadeCustody, RippleCustody, SimpleXRPL } from 'simplexrpl'\n\n// A common institutional split: the issuer is held in Ripple Custody (governed\n// approvals), the distribution/hot wallet in Palisade. One client drives both.\n// Config comes from the environment / your secrets manager.\nconst custody = await RippleCustody.fromEnv({\n  primary: process.env.RIPPLE_CUSTODY_PRIMARY_ADDRESS ?? '',\n})\nconst palisade = await PalisadeCustody.create({\n  baseUrl: 'https://api.sandbox.palisade.co', // sandbox (TESTNET data)\n  // Two credentials: a wallet-read one (discovery) and a transactions one.\n  credentials: {\n    wallets: {\n      clientId: process.env.PALISADE_WALLETS_CLIENT_ID ?? '',\n      clientSecret: process.env.PALISADE_WALLETS_CLIENT_SECRET ?? '',\n    },\n    transactions: {\n      clientId: process.env.PALISADE_TX_CLIENT_ID ?? '',\n      clientSecret: process.env.PALISADE_TX_CLIENT_SECRET ?? '',\n    },\n  },\n  primary: {\n    vaultId: process.env.PALISADE_VAULT_ID ?? '',\n    walletId: process.env.PALISADE_WALLET_ID ?? '',\n  },\n})\n\nconst client = await SimpleXRPL.init({\n  xrpldUrl: 'wss://s.altnet.rippletest.net:51233', // XRPL Testnet\n  signers: [custody, palisade],\n})\n\n// The distribution/hot wallet on the Palisade connector.\nconst hotWallet = client.resolveAccount(palisade.primary.address)\n\n// Each step targets a different custodian, and the client routes each call to\n// the connector that owns the acting account. Issue a USD IOU with the Custody\n// account as issuer (the primary signer, default `from`) and the Palisade wallet\n// as the holder that extends trust — a genuinely cross-custodian issuance, since\n// `issue` sequences AccountSet (issuer) → TrustSet (holder) → Payment (issuer).\n// Naming `holder` is what selects this path; omitting it would instead bootstrap\n// both accounts from the local `XRPL_ISSUER_SEED` / `XRPL_HOT_WALLET_SEED` seeds.\nawait client.iou.issue({\n  ticker: 'USD',\n  holder: hotWallet.address,\n  amount: '1000',\n})\n\n// Then pay out from the Palisade hot wallet via `from`.\nawait client.xrp.transfer(\n  { to: 'rBeneficiary00000000000000000000000', amount: '25' },\n  { from: hotWallet.address },\n)\n\nawait client.disconnect()\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"see-also","__idx":1},"children":["See Also"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/simplexrpl/references/verticals/iou/issue"},"children":["iou.issue()"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/simplexrpl/references/verticals/xrp/transfer"},"children":["xrp.transfer()"]}]}]}]},"headings":[{"value":"Run A Workflow Across Custodians","id":"run-a-workflow-across-custodians","depth":1},{"value":"See Also","id":"see-also","depth":2}],"frontmatter":{"seo":{"description":"Drive accounts held by different custodians from a single client, routing each operation to the connector that owns the account.","title":"Run A Workflow Across Custodians"},"labels":["simpleXRPL","SDK"]},"editPage":{"to":"https://github.com/ripple/opensource.ripple.com/tree/main/docs/simpleXRPL/tutorials/cross-custodian-workflows.md"},"lastModified":"2026-08-26T05:15:32.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/simplexrpl/tutorials/cross-custodian-workflows","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}