RFC: Funding Modality - #339
Open
CoachCoe wants to merge 2 commits into
Open
Conversation
Introduce a `Funding` trait defining the runtime contract for the funding modality: moving value across the boundary between a user's Polkadot balance and everything outside it, in both directions, over card/bank, cross-chain swap, and peer-to-peer cash rails. Four methods, wire ids 168-179: - `request` / `status_subscribe` for consumers declaring and watching an intent, with an opaque `resume` context so a session can resume the action an insufficient balance interrupted. - `serve_subscribe` / `report` for providers that declare the modality in their manifest, receive intents addressed to them, and report progress. The two directions carry different guarantees, and the status type says so: `Delivered` is emitted only on the host's own on-chain observation of arrival, while `Released` asserts only that funds left under the user's authorization. No host can verify that cash reached a hand or a bank account, so the protocol declines to offer a shared "done" state that would let the UI claim otherwise. Route declaration is static and lives in the product manifest, so the host builds the rail list from dotNS records without launching any provider. Eligibility resolves host-side and reaches products as reason codes rather than rules, keeping the operator's jurisdiction matrix out of the wire format. `ProductRuntimeHost` gains an empty `Funding` impl; every method defaults to `CallError::unavailable()` until a host implements the modality.
…mples Codegen emits fieldless SCALE enums as TypeScript string unions, so FundingDirection is "In" | "Out" rather than a tagged variant. The generated examples failed the playground's tsc pass over test/generated/examples.
|
claude I'm not reading that, if you want me to confirm the design works with coinage, give a list of concise requirements. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RFC: Funding Modality
Summary
Adds a
Fundingtrait defining the runtime contract for a new funding modality: the one surface for moving value across the boundary between a user's Polkadot balance and everything outside it, in both directions, over the rails the product needs — a card or bank transfer, a swap from another chain, and cash traded in person with a peer.Consumers (the host's balance card, or any product that hits an insufficient balance mid-flow) declare an intent and watch it to completion. Providers declare the modality in their manifest, receive intents addressed to them, and report progress. Four methods, wire ids 168–179.
The directions are deliberately asymmetric and the types say so. Inbound, the host proves the outcome and emits
Deliveredfrom its own on-chain observation. Outbound, it can prove only that funds left under the user's authorization and emitsReleased, because no host can verify that cash reached a hand or a bank account. Collapsing both into one "done" state would let the UI tell a user their money arrived when the protocol has no idea, so the protocol declines to offer it.Checklist
docs/rfcs/funding-modality.md(no number — CI assigns one on merge)rust/crates/truapi/rfcMotivation
Two gaps, one in each direction.
In: the only funding path is
PaymentTopUpSource—ProductAccountandPrivateKeyfrom RFC 0006, plusCoinsfrom RFC 0021. All three mean the product already controls this money. None answer where money comes from, so a product reachingHostPaymentError::InsufficientBalancehas nowhere to send the user.Out:
Payment::requestsends to an account, which moves value sideways rather than out. A user wanting cash and a business wanting collected funds in a bank account are the same gap reversed.Without a declared contract, each on-ramp arrives as bespoke host code with its own screens, credential handling, and idea of what "done" means — and three providers in, the host owns three fiat relationships it should never have touched.
Two routes need no partner at all (
Friend,ExternalWallet), which is what makes the modality shippable and testable end to end before any partner terms exist. The other three rails are additive: one manifest declaration and one provider each, with no change to this contract.What's in scope, and what the host owns
The modality is mostly host UI. A provider contributes at one of two levels — static manifest metadata for its row, or its own App executable framed in a surface the host can reclaim. Twelve of the design's fourteen states are host-owned; the RFC maps each one. A provider never draws the stamp bar, a terminal state, the rail list, the eligibility explanation, or the authorization to release funds.
There is deliberately no level where a provider supplies a UI fragment the host renders natively. That would need
CustomRendererNodeto flow product → host, and it currently flows only host → product. It is excluded for a substantive reason rather than a mechanical one: without a constrained renderer profile, a provider could render "Delivered" or "Verified" in host styling. See Out of Scope in the RFC.Dependencies on in-flight work
secrets.requestalready covers it. Itspersonhoodcaller tier is also the mechanism I'd evaluate first for reusable verification. cc @BigTavafundingto a worker'sincludes, plus afunding.routesblock declaring rail, directions, assets, latency, and whether an account is required. That RFC states per-modality runtime contracts belong in their own RFCs; this is that contract.includes.If either PR lands first and shifts ids, this block moves up rather than reusing anything.
Coinage steward
Per the TrUAPI Steward process, the surface for a component should not change without its steward in the loop. Funding settles through Coinage: inbound delivery targets are
CoinPaymentreceivables, and theFriendroute rests oncreate_receivable+listen_for_payment. cc @gui1117 — a review pass on whetherFundingDelivery::Receivableand the friend route match what Coinage actually does would be valuable before this is discussed in the Monday sync.Product SDK
Products consume the Product SDK rather than the host directly, so a new modality implies SDK work (cc @TarikGul) — at minimum a typed wrapper over
funding.request/statusSubscribe, and for provider products the serve/report loop. Naming it here so it is tracked rather than discovered later.Not yet implemented — expected, not broken
ProductRuntimeHostgains an emptyimpl Funding, matching the existingimpl Chat/impl CoinPaymentpattern. Every method defaults toCallError::unavailable(), so nothing is stubbed as working.Consequently the four generated TypeScript examples (
playground/test/generated/examples/funding-*.ts) assertisOk()and will fail against a live host until a host implements the modality. That is the same position #335 is in, and it is the codegen convention (64 methods, 64 executable examples) rather than a broken test.Verification
cargo build --workspace --all-targets --all-features— clean, no warningscargo +nightly fmt --check— cleancargo clippy --workspace --all-targets --all-features -- -D warnings— cleancargo test --workspace --all-features— 609 passed, 0 failednpm run build(tsc) +npm test(bun) — clean; 215 passed, 0 failed./scripts/codegen.shre-run; dispatcher, wire table, TS client, and golden fixtures regenerated. Both golden updates are purely additive — zero removed lines.Note:
rust/crates/truapi-codegen/tests/golden/wire_table.rsis also touched by #335, so expect a trivial conflict there.Open questions
Recorded in the RFC rather than answered; the types are neutral to each answer.
Releasedis where this RFC stops. Closing it means escrow (making the host an adjudicator), a provider attestation surfaced as a claim, or a dispute path with a human — three different trust models with different liability. A decision about accountability before it is a protocol detail.FundingAsset.FundingAssetopacity survive?🤖 Generated with Claude Code