A marketplace payment, start to finish
Every step below is real, executable TypeScript — no invented IDs, no illustrative pseudocode —
re-verified live 2026-08-31 against the Network Execution Engine. A buyer pays into a marketplace;
executeSettlement() computes the Fee and Split and builds the payout's SigningRequest itself;
the marketplace signs it with its own execution wallet. Ishtaran never sees a private key.
The mechanism
Three parties, one rule: the private key that authorizes the payout is generated and used inside the marketplace's own backend, and never crosses the wire. Ishtaran verifies the signature and relays the transaction — it cannot forge one.
Step by step
Code is TypeScript here; the same calls exist in Java, Python, and Go with each language's own idiom — see the full, runnable version linked at the end of each SDK's install section below.
1. Self-service signup
One call provisions the Organization, a default Application, its Sandbox Environment, and a first API Key — no manual dashboard step.
const owner = IshtaranClient.create({ environment: Environment.Sandbox });
const signup = await owner.auth.signUp('Ishtaran Marketplace Demo', 'owner@example.com', '••••••••');
2. The marketplace's own execution wallet
Mnemonic and private key are generated in this process and never serialized anywhere. Only the extended public key is sent to Ishtaran.
const wallet = walletModule.generate(); // 24-word mnemonic, local only
const registered = await client.wallets.register(
applicationId, networkId, DerivationScheme.TRON_BIP44_HARDENED_ACCOUNT,
wallet.wallet.accountExtendedPublicKey, idempotencyKey,
);
3. Seller claims their own identity
The marketplace issues an invitation; the seller — a separate session, never authenticated as the
marketplace — claims it and gets their own AccountHolder login. Authorizing an Account for an
Application (next step) requires the marketplace's Member session, not the API Key — a real
gap this flow found in the docs, now fixed.
const invite = await client.accounts.createAccountHolderInvitation(organizationId, 'seller-01');
// -- delivered out-of-band (email/link) to the seller --
const claim = await sellerClient.accountHolders.signUpAndClaimInvitation(
invite.plainTextToken, 'seller@example.com', '••••••••',
);
4. Register where every beneficiary gets paid, and who pays for network execution
Before the first real Settlement on this AssetNetwork: an ExecutionDestination for the seller
(their own external wallet) and for the marketplace's own commission Account, and a
NetworkCostPayerAccount to cover real network execution cost — the marketplace's own commission
Account again, a real business decision (it pays network cost out of its own commission). Skip
either and executeSettlement() in step 8 fails before it builds anything.
await client.executionDestinations.register(organizationId, sellerAccountId, assetNetworkId, sellerOwnAddress);
await client.executionDestinations.register(organizationId, marketplaceRevenueAccountId, assetNetworkId, marketplaceRevenueAddress);
await client.networkCostPayerAccounts.register(organizationId, assetNetworkId, marketplaceRevenueAccountId);
5. Transaction + Payment Intent
Two non-payer Participants (seller, marketplace) require an explicit Split — a single implicit
100% only applies with exactly one beneficiary (BR-SPL-004). workflowVersionId is null here
— genuinely optional, this flow's own application decides when to settle.
const txn = await client.transactions.create(organizationId, applicationId, environmentId, null, assetNetworkId, '1000', [payer, seller, marketplace]);
const intent = await client.deposits.createPaymentIntent(organizationId, txn.transactionId, assetNetworkId, '1000');
6. Deposit lands, reservation is automatic
Once the deposit is confirmed, the Transaction moves to Reserved on its own — no explicit
reserve() call needed in the common path.
await client.sandbox.simulateDeposit(environmentId, depositAddress, assetNetworkId, '1000');
await client.sandbox.simulateConfirmation(environmentId, observedAddressId, 1, true);
// poll transactions.getState(...) -- CREATED -> RESERVED
7. Settlement builds its own SigningRequest — no separate call needed
executeSettlement() computes the Fee/Split AND builds a real SigningRequest itself, under
SelfCustody — one ExecutionLeg per beneficiary, each addressed via the ExecutionDestination
registered in step 4. This is not a two-step process — there is no separate
signingRequests.create(...) call for a Settlement's payout; fetch the full Settlement to read
the signingRequestId it already produced.
const executed = await client.settlements.executeSettlement(txn.transactionId);
const settlement = await client.settlements.get(executed.settlementId);
// settlement.signingRequestId is already populated -- nothing left to "request"
8. Sign locally, submit, verify
The private key touches exactly one line, in this process. Ishtaran rejects a tampered hash
outright (SIGNED_TRANSACTION_MISMATCH) and only broadcasts once every leg is verified — the
first submission alone never triggers it.
const signingRequest = await client.signingRequests.get(settlement.signingRequestId!);
for (const leg of signingRequest.legs) {
const signature = wallet.signer.sign(signingRequest.derivationReference, hexToBytes(leg.canonicalHash));
await client.signingRequests.submitSignedTransaction(
settlement.signingRequestId!, leg.executionLegId, leg.canonicalHash, bytesToHex(signature),
);
}
Both legs reach Broadcast with a real (simulated) reference, automatically, on the last
signature — no separate "broadcast" call. In Production, the only thing that changes is what's
behind the broadcast port — the signing protocol above is identical. Once each leg's confirmation
is simulated and the Settlement reaches Completed, payout.getPayableSummary(...) reflects
what was actually paid — never ledger.getBalance().available, since both beneficiaries'
ExecutionDestinations are external wallets (see Self-Custody and
Transaction, Settlement, Split, and Refund).
Run it yourself
The full runnable version of this exact flow — same calls, same order — ships as example 14 in the TypeScript SDK, re-verified live 2026-08-31:
- TypeScript/Node.js:
14-marketplace-journey.ts - Java:
Example14MarketplaceJourney.java - Python:
14_marketplace_journey.py - Go:
14_marketplace_journey
See Self-Custody for the full signing-protocol detail, and SDKs for installation.