@mysten/docs 0.1.32 → 0.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -81,6 +81,14 @@
81
81
  - [App Integration](./slush-wallet/dapp.md): Connect your app to Slush Wallet using the Slush Wallet SDK and wallet standard.
82
82
  - [Deep Linking](./slush-wallet/deep-linking.md): Configure deep links to open Slush Wallet directly to specific screens or actions.
83
83
 
84
+ ## Sponsor
85
+ > Build gas sponsorship services with pluggable validation
86
+
87
+ - [Sponsor](./sponsor.md): Build gas sponsorship services with pluggable transaction validation
88
+ - [Basic Usage](./sponsor/basic-usage.md): Build a service that validates and executes user-signed sponsored bytes
89
+ - [Validation](./sponsor/validators.md): Compose built-in and custom validation analyzers for sponsorship policy
90
+ - [Best Practices](./sponsor/best-practices.md): Operate a Sui sponsor with clear policy, gas limits, and outcome handling
91
+
84
92
  ## Sui SDK
85
93
  > TypeScript interfaces for Sui
86
94
 
@@ -96,10 +104,15 @@
96
104
  - [Coins and Balances](./sui/transactions/coins-and-balances.md): Work with coin objects and address balances in transactions
97
105
  - [Commands and Inputs Reference](./sui/transactions/reference.md): Complete reference for transaction commands and input types
98
106
  - [Building Offline](./sui/transactions/offline.md): Build transactions without a network connection
99
- - [Key pairs](./sui/cryptography/keypairs.md): Create and manage Ed25519, Secp256k1, and Secp256r1 keypairs for Sui transaction signing.
100
- - [Multi-Signature Transactions](./sui/cryptography/multisig.md): Create multi-signature transactions with multiple signers on Sui.
101
- - [Passkey](./sui/cryptography/passkey.md): Use WebAuthn passkeys for Sui transaction signing.
102
- - [Web Crypto Signer](./sui/cryptography/webcrypto-signer.md): Sign transactions using the Web Crypto API for secure browser-based key management.
107
+ - [Cryptography](./sui/cryptography.md): Sign and verify Sui transactions and messages with keypairs and external signers
108
+ - [Key pairs](./sui/cryptography/keypairs.md): Create Ed25519, Secp256k1, and Secp256r1 keypairs and derive them from mnemonics and secret keys
109
+ - [Signers](./sui/cryptography/signers.md): Signer implementations beyond keypairs — cloud KMS, Ledger, Web Crypto, passkeys, and multisig
110
+ - [AWS KMS Signer](./sui/cryptography/signers/aws-kms.md): Sign Sui transactions with a key stored in AWS Key Management Service
111
+ - [GCP KMS Signer](./sui/cryptography/signers/gcp-kms.md): Sign Sui transactions with a key stored in Google Cloud Key Management Service
112
+ - [Ledger Signer](./sui/cryptography/signers/ledger.md): Sign Sui transactions with a Ledger hardware wallet
113
+ - [Web Crypto Signer](./sui/cryptography/signers/webcrypto.md): Sign transactions using the Web Crypto API for secure browser-based key management
114
+ - [Passkey](./sui/cryptography/signers/passkey.md): Use WebAuthn passkeys for Sui transaction signing.
115
+ - [Multi-Signature Transactions](./sui/cryptography/signers/multisig.md): Create multi-signature transactions with multiple signers on Sui.
103
116
  - [The `@mysten/sui/utils` package](./sui/utils.md): Utility functions for addresses, coins, and common operations in the Sui TypeScript SDK.
104
117
  - [Derived Objects](./sui/utils/derived_objects.md): Compute derived object IDs from parent objects for deterministic offline derivation.
105
118
  - [BCS](./sui/bcs.md): Binary Canonical Serialization for encoding Sui Move types.
@@ -0,0 +1,170 @@
1
+ # Basic Usage
2
+
3
+ > Build a service that validates and executes user-signed sponsored bytes
4
+
5
+ In the examples below, `userSigner` is the user's `Signer`. In an app, the user signs through
6
+ dapp-kit's `signTransaction` instead.
7
+
8
+ ## Recommended flow
9
+
10
+ Prefer services that expect **already-built bytes plus the user's signature**:
11
+
12
+ 1. The client gets the sponsor address.
13
+ 2. The client builds final transaction bytes with the sponsor as gas owner and address-balance gas.
14
+ 3. The user signs those exact bytes.
15
+ 4. The backend validates the bytes, co-signs, and executes.
16
+
17
+ This means the sponsor knows exactly what it is co-signing, there is no half-signed transaction
18
+ waiting on a user after the sponsor signs, and the flow avoids an extra sponsor-build round trip
19
+ before the user can sign.
20
+
21
+ ## Provide the sponsor address
22
+
23
+ The client needs a sponsor address before it builds the transaction. For services where the sponsor
24
+ account might rotate, expose the current address through a small config endpoint:
25
+
26
+ ```ts
27
+ app.get('/config', (c) => c.json({ sponsor: sponsor.address }));
28
+ ```
29
+
30
+ Prefer fetching this value dynamically when you want the backend to rotate the sponsor key or move
31
+ sponsorship to a different account without forcing client deployments. Use the chosen address as the
32
+ transaction gas owner. In local examples where the client and sponsor live in the same process,
33
+ `sponsor.address` is the same value.
34
+
35
+ ## Client builds the transaction
36
+
37
+ The client sets the **sender**, the **gas owner to the sponsor**, and the **gas payment to `[]`**
38
+ (address-balance gas). The user signs those final bytes and sends them to the sponsor service:
39
+
40
+ ```ts
41
+
42
+ const { sponsor } = await fetch('/config').then((res) => res.json());
43
+
44
+ transaction.setSender(userAddress);
45
+ transaction.setGasOwner(sponsor); // gas is paid by the sponsor…
46
+ transaction.setGasPayment([]); // …from its address balance (no specific gas coins)
47
+ const bytes = await transaction.build({ client });
48
+
49
+ const { signature: userSignature } = await userSigner.signTransaction(bytes);
50
+
51
+ await fetch('/sponsor', {
52
+ method: 'POST',
53
+ headers: { 'content-type': 'application/json' },
54
+ body: JSON.stringify({ transaction: toBase64(bytes), userSignature }),
55
+ });
56
+ ```
57
+
58
+ The backend validates those bytes, co-signs, and executes:
59
+
60
+ ```ts
61
+ const result = await sponsor.signAndExecuteTransaction({
62
+ transaction: body.transaction,
63
+ userSignature: body.userSignature,
64
+ });
65
+
66
+ switch (result.$kind) {
67
+ case 'Rejected': // a validator declined; the sponsor never signed or executed
68
+ throw new Error(result.issues.map((issue) => issue.message).join('; '));
69
+ case 'FailedTransaction': // executed onchain but aborted, the sponsor still paid gas
70
+ throw new Error(`Transaction failed onchain: ${result.FailedTransaction.digest}`);
71
+ case 'Transaction': // executed successfully
72
+ return result.Transaction.digest;
73
+ }
74
+ ```
75
+
76
+ `signAndExecuteTransaction` has **three** outcomes: a policy `Rejected` (never executed), a
77
+ `FailedTransaction` (executed but aborted onchain, where the sponsor still pays gas either way), and
78
+ a successful `Transaction`. The non-obvious one is `FailedTransaction`: a result that isn't
79
+ `Rejected` still isn't necessarily a success.
80
+
81
+ Pass `include` when the service needs extra transaction details in the response. The response always
82
+ includes effects because the service needs them to distinguish successful execution from failed
83
+ execution.
84
+
85
+ ## A user transaction
86
+
87
+ A sponsored transaction operates on the **user's** objects, never the gas coin. When the user spends
88
+ SUI, source it from their own balance with `useGasCoin: false`:
89
+
90
+ ```ts
91
+ function userPayment(amount: bigint) {
92
+ const tx = new Transaction();
93
+ const coin = tx.coin({ balance: amount, useGasCoin: false });
94
+ tx.transferObjects([coin], recipient);
95
+ return tx;
96
+ }
97
+ ```
98
+
99
+ ## Over the network
100
+
101
+ The methods are transport-agnostic: they take transaction bytes as `Uint8Array` or base64 strings,
102
+ and user signatures as signature strings. You own the HTTP shape, so serialize binary fields as
103
+ base64. The client side is exactly the [build-and-sign flow above](#client-builds-the-transaction);
104
+ the backend maps each result `$kind` to a response. Every outcome is a `$kind`, so no try/catch is
105
+ needed for a policy rejection:
106
+
107
+ ```ts
108
+ // Server handler — wire it into your framework's route however you like.
109
+ async function handleSponsorRequest(body: { transaction: string; userSignature: string }) {
110
+ const result = await sponsor.signAndExecuteTransaction({
111
+ transaction: body.transaction,
112
+ userSignature: body.userSignature,
113
+ });
114
+ switch (result.$kind) {
115
+ case 'Rejected':
116
+ return { ok: false as const, issues: result.issues };
117
+ case 'FailedTransaction':
118
+ return { ok: false as const, issues: [{ message: 'Transaction failed on-chain.' }] };
119
+ case 'Transaction':
120
+ return { ok: true as const, digest: result.Transaction.digest };
121
+ }
122
+ }
123
+ ```
124
+
125
+ Need per-request context, such as an auth token or tenant id? Validators read it from `options`, and
126
+ the sponsor requires it as typed `validationOptions`. See [Validation](/sponsor/validators). Handle
127
+ replay prevention and rate limiting in your service.
128
+
129
+ For a complete Hono server using this shape, see the
130
+ [sponsor server example](https://github.com/MystenLabs/ts-sdks/tree/main/packages/sponsor/examples/sponsor-server).
131
+
132
+ ## Gas and unresolved transactions
133
+
134
+ When the client builds, the user already built and signed final bytes, so they must be resolved. The
135
+ sponsor never rebuilds them.
136
+
137
+ When the sponsor builds the transaction, what you pass to `signTransaction` can be **unresolved**,
138
+ with unresolved object inputs and no gas budget being fine. The sponsor sets itself as gas owner
139
+ with address-balance gas and calls `build({ client })`, which in one pass resolves object inputs,
140
+ sets the gas price and expiration, and (unless the transaction already pins one with
141
+ `tx.setGasBudget(…)`) **estimates the gas budget by dry-running**. Validation then runs on the
142
+ _built_ transaction, so `gasBudget({ max })` checks the resolved budget and analyzers see the exact
143
+ bytes that get signed.
144
+
145
+ ## Advanced: sponsor builds the transaction
146
+
147
+ > **Warning:** For public sponsorship services, prefer accepting final bytes plus `userSignature`. Use the
148
+ > sponsor-builds flow only when the backend owns transaction construction and you understand the
149
+ > trust boundary.
150
+
151
+ Alternatively, hand the sponsor the user's commands. The sponsor sets itself as gas owner,
152
+ builds\n(address-balance gas, and a dry-run-estimated budget unless you pinned one), validates, and
153
+ signs. The user then signs the returned bytes, and both signatures execute together. `sender` is
154
+ optional, applied only when the transaction doesn't already set one (for example, bare commands):
155
+
156
+ ```ts
157
+ const result = await sponsor.signTransaction({
158
+ transaction,
159
+ sender: userAddress, // optional — only used if `transaction` has no sender
160
+ });
161
+ if (result.$kind === 'Rejected') {
162
+ throw new Error(result.issues.map((issue) => issue.message).join('; '));
163
+ }
164
+
165
+ const { signature: userSignature } = await userSigner.signTransaction(result.bytes);
166
+ await client.core.executeTransaction({
167
+ transaction: result.bytes,
168
+ signatures: [userSignature, result.sponsorSignature],
169
+ });
170
+ ```
@@ -0,0 +1,163 @@
1
+ # Best Practices
2
+
3
+ > Operate a Sui sponsor with clear policy, gas limits, and outcome handling
4
+
5
+ Gas sponsorship is a backend operating boundary. The user supplies transaction bytes; the sponsor
6
+ decides whether to pay for them. Treat every sponsored transaction as untrusted input until your
7
+ policy validates the exact bytes that get signed.
8
+
9
+ ## Prefer user-signed transaction bytes
10
+
11
+ For public sponsor services, prefer accepting **already-built bytes plus `userSignature`**. The
12
+ client builds the transaction with the sponsor as gas owner, the user signs those final bytes, and
13
+ the sponsor validates, co-signs, and executes.
14
+
15
+ This keeps the safer trust boundary: the sponsor only signs after the user has signed the exact
16
+ transaction bytes. See [Basic Usage](/sponsor/basic-usage) for the recommended flow.
17
+
18
+ ## Understand the Sui gas model
19
+
20
+ The Sponsor SDK uses address-balance gas:
21
+
22
+ ```ts
23
+ transaction.setGasOwner(sponsorAddress);
24
+ transaction.setGasPayment([]);
25
+ ```
26
+
27
+ An empty gas payment means the sponsor pays from its SUI address balance. This avoids locking
28
+ specific gas coin objects and allows parallel sponsor execution, but it also means the sponsor's
29
+ address balance is the operating balance at risk.
30
+
31
+ Fund the address balance intentionally. Faucet coins are coin objects; move SUI into the address
32
+ balance with a transaction such as `0x2::coin::send_funds`.
33
+
34
+ ## Constrain sponsorship to product flows
35
+
36
+ Never expose an endpoint that blindly co-signs any user transaction. At minimum, constrain:
37
+
38
+ - **Allowed targets:** Use `allowedFunctions()` for fully qualified `package::module::function`
39
+ targets, not broad package allowlists, when the product flow only needs one function.
40
+ - **PTB templates:** Validate the full Programmable Transaction Block shape your product expects:
41
+ command count, command kinds, Move targets, type arguments, argument sources, object ownership,
42
+ and value movement. A function allowlist is a starting point, not a complete policy.
43
+ - **Gas budget:** Add `gasBudget({ max })` so a valid but expensive transaction cannot use the
44
+ protocol maximum.
45
+ - **Sender signature:** Add `userSignatureMatchesSender()` so the supplied signature matches the
46
+ transaction sender before the sponsor co-signs.
47
+ - **Value movement:** Use analyzers such as `balanceFlows` when sponsorship depends on payment,
48
+ refunds, or tenant-specific balances.
49
+
50
+ The
51
+ [sponsor server example](https://github.com/MystenLabs/ts-sdks/tree/main/packages/sponsor/examples/sponsor-server)
52
+ uses a basic PTB template: exactly one command, and that command must be a call to one configured
53
+ `package::module::function`. Real products should make the template as specific as their flow
54
+ requires.
55
+
56
+ ## Keep `defaults()`
57
+
58
+ `defaults()` protects against common sponsorship mistakes:
59
+
60
+ | Validator | Why it matters |
61
+ | ------------------------- | ----------------------------------------------------------------------------- |
62
+ | `validSender()` | Rejects unset senders and transactions where the sender is the sponsor |
63
+ | `onlyAddressBalanceGas()` | Requires empty gas payment so users cannot nominate sponsor gas coin objects |
64
+ | `gasCoinNotUsed()` | Rejects commands that use `tx.gas`, which belongs to the sponsor |
65
+ | `onlySenderWithdrawals()` | Rejects address-balance withdrawals from the sponsor or any non-sender source |
66
+ | `simulationSucceeds()` | Rejects transactions that dry-run as aborting |
67
+ | `boundedExpiration()` | Rejects missing or too-far-ahead transaction expirations |
68
+
69
+ Once you pass `validate`, defaults no longer run automatically. Include `defaults()` explicitly:
70
+
71
+ ```ts
72
+ const sponsor = createSponsor({
73
+ signer,
74
+ client,
75
+ validate: [defaults(), gasBudget({ max: 50_000_000n }), allowedFunctions([target])],
76
+ });
77
+ ```
78
+
79
+ ## Gas budget is not the whole economic risk
80
+
81
+ A gas cap limits how much gas the sponsor is willing to cover, but it is not a full business-policy
82
+ check.
83
+
84
+ Sui transactions can create, mutate, transfer, share, and delete objects. Storage costs and storage
85
+ rebates can create economic incentives that are not obvious from the gas budget alone. If you
86
+ sponsor arbitrary transactions, a user can try to externalize costs to the sponsor while directing
87
+ objects, balances, or rebate-related value to themselves or another address.
88
+
89
+ For product flows with financial value, validate the command shape and inspect net value movement:
90
+
91
+ ```ts
92
+
93
+ const USDC = '0x...::usdc::USDC';
94
+
95
+ const requirePayment = createAnalyzer({
96
+ dependencies: { balanceFlows: analyzers.balanceFlows },
97
+ analyze:
98
+ () =>
99
+ ({ balanceFlows }) => {
100
+ const paidToSponsor =
101
+ balanceFlows.sponsor
102
+ ?.filter((flow) => flow.coinType === USDC)
103
+ .reduce((sum, flow) => sum + flow.amount, 0n) ?? 0n;
104
+
105
+ return paidToSponsor >= 10_000n
106
+ ? { result: null }
107
+ : { result: [{ code: 'UNDERPAID', message: 'Sponsor was not paid enough.' }] };
108
+ },
109
+ });
110
+ ```
111
+
112
+ ## Keep sponsor balances intentional
113
+
114
+ If the sponsor signer is loaded from an environment variable, treat it as a hot key. Keep only the
115
+ SUI needed for short operating windows in the sponsor address balance, and top it up from a more
116
+ protected account.
117
+
118
+ For production services, prefer a managed signer:
119
+
120
+ - Use a KMS-backed signer such as `@mysten/aws-kms-signer` or `@mysten/gcp-kms-signer`.
121
+ - Restrict who can deploy or read the signer configuration.
122
+ - Separate the sponsor hot account from treasury accounts.
123
+ - Rotate keys and drain old sponsor balances when access changes.
124
+
125
+ The Sponsor SDK accepts any `Signer`, so KMS signers can be used in place of an in-memory keypair.
126
+
127
+ ## Dry-run is not an execution guarantee
128
+
129
+ `simulationSucceeds()` dry-runs the transaction before signing, but chain state can change before
130
+ execution. A transaction that passed simulation can still abort later and charge the sponsor gas.
131
+
132
+ Use dry-run as one policy input, not as the only control. Add:
133
+
134
+ - gas caps,
135
+ - target allowlists,
136
+ - command-shape checks,
137
+ - rate limits,
138
+ - authentication,
139
+ - per-user or per-tenant quotas.
140
+
141
+ Onchain replay isn't something you handle here. Sui executes a signed transaction at most once, and
142
+ `boundedExpiration()` (a default) supplies the bounded expiration it requires for address-balance
143
+ gas. Application-level idempotency is still yours: if a flow should be sponsored only once per user,
144
+ dedupe it with a request ID, nonce, or stored intent.
145
+
146
+ ## Handle rejection and failed execution separately
147
+
148
+ A `Rejected` result means the sponsor did not sign or execute. A `FailedTransaction` means the
149
+ transaction executed onchain and failed, so the sponsor might have paid gas.
150
+
151
+ ```ts
152
+ switch (result.$kind) {
153
+ case 'Rejected':
154
+ return { ok: false, status: 403, issues: result.issues };
155
+ case 'FailedTransaction':
156
+ return { ok: false, status: 502, digest: result.FailedTransaction.digest };
157
+ case 'Transaction':
158
+ return { ok: true, digest: result.Transaction.digest };
159
+ }
160
+ ```
161
+
162
+ Do not collapse those outcomes into a single error. They have different accounting and retry
163
+ semantics.
@@ -0,0 +1,114 @@
1
+ # Sponsor
2
+
3
+ > Build gas sponsorship services with pluggable transaction validation
4
+
5
+ > **Warning:** **Incubation package.** This package is still experimental and might change without notice. Pin
6
+ > versions and review changes before upgrading.
7
+
8
+ Primitives for building gas-sponsorship flows on Sui. You are the **sponsor operator**: you bring a
9
+ `Signer` (the sponsor) and a policy for what you'll pay for, and `createSponsor` takes a user's
10
+ transaction, fills in the gas, validates it against that policy, and adds the sponsor's signature.
11
+ The hard part, validating what you're sponsoring, is handled by a pluggable pipeline backed by the
12
+ [`@mysten/wallet-sdk` transaction analyzer](https://www.npmjs.com/package/@mysten/wallet-sdk).
13
+
14
+ The transaction comes from your user; you decide what's allowed and cover the gas.
15
+
16
+ ## Install
17
+
18
+ ```npm
19
+ npm i @mysten-incubation/sponsor @mysten/sui
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```ts
25
+
26
+ const client = new SuiGrpcClient({
27
+ network: 'testnet',
28
+ baseUrl: 'https://fullnode.testnet.sui.io:443',
29
+ });
30
+
31
+ const sponsor = createSponsor({
32
+ signer: Ed25519Keypair.fromSecretKey(process.env.SPONSOR_KEY!),
33
+ client,
34
+ // `defaults()` keeps the baseline (see Defaults).
35
+ validate: [defaults(), gasBudget({ max: 50_000_000n }), allowedFunctions(['0xabc::shop::buy'])],
36
+ });
37
+ ```
38
+
39
+ ## How gas works
40
+
41
+ The sponsor pays gas from its **address balance**. The empty gas payment (`setGasPayment([])`) draws
42
+ from the sponsor's onchain SUI balance rather than from specific gas coins. This means the sponsor
43
+ can sign and execute in parallel:
44
+
45
+ Address-balance gas doesn't lock specific coin objects, so concurrent sponsored transactions don't
46
+ contend for gas coins. A sender that reuses its own versioned objects (coins) across rapid
47
+ transactions still has to serialize those, but that's the sender's concern, not the sponsor's.
48
+
49
+ You fund the address balance by depositing SUI into it (for example, a one-time
50
+ `0x2::coin::send_funds(coin, sponsorAddress)`); the faucet only hands out coin objects, not address
51
+ balance.
52
+
53
+ Because gas is the sponsor's, **a sponsored transaction must never use the gas coin** (`tx.gas`).
54
+ `gasCoinNotUsed()` (a default) enforces this. When the user needs SUI, they spend their own with
55
+ `tx.coin({ balance, useGasCoin: false })`.
56
+
57
+ ## Defaults
58
+
59
+ If you pass no `validate`, the sponsor runs `defaults()`: `validSender()`,
60
+ `onlyAddressBalanceGas()`, `gasCoinNotUsed()`, `onlySenderWithdrawals()`, `simulationSucceeds()`,
61
+ and `boundedExpiration()`. Once you add your own validators they no longer run automatically; drop
62
+ them back in as one entry (the `validate` array flattens nested arrays):
63
+
64
+ ```ts
65
+ createSponsor({ signer, client }); // runs defaults()
66
+ createSponsor({ signer, client, validate: [defaults(), allowedFunctions(['0xabc::shop::buy'])] });
67
+ ```
68
+
69
+ Each default guards something real: `validSender()` requires a sender and rejects transactions where
70
+ the sponsor is also the sender; `onlyAddressBalanceGas()` and `gasCoinNotUsed()` stop a caller from
71
+ spending the sponsor's gas (its address balance pays, and the gas coin is the sponsor's);
72
+ `onlySenderWithdrawals()` rejects any `FundsWithdrawal` input that isn't the sender's, including one
73
+ from the sponsor's **address balance** (the same balance that pays gas, a direct drain the gas-coin
74
+ check can't see, because the withdrawal is an input, not a command argument); `simulationSucceeds()`
75
+ avoids paying for a transaction that aborts; and `boundedExpiration()` requires a bounded expiration
76
+ for address-balance gas.
77
+
78
+ Two things the defaults **don't** do, by design (handle them at your service boundary):
79
+
80
+ - **No gas ceiling.** A legitimate but expensive transaction can cost up to the protocol max. Add
81
+ `gasBudget({ max })` for a cap, and rate-limit / authenticate callers.
82
+ - **No execution guarantee.** `simulationSucceeds()` is a dry-run; onchain state can shift before
83
+ execution, so a later-aborting sponsored transaction can still charge the sponsor gas.
84
+
85
+ Keep `defaults()` unless you're deliberately replacing those checks.
86
+
87
+ For **offline-only signing** (no dry-run), use validators that read only `data` (no
88
+ `transactionResponse`). Nothing depends on simulation, so the sponsor never simulates:
89
+
90
+ ```ts
91
+ createSponsor({ signer, client, validate: [validSender(), gasCoinNotUsed()] });
92
+ ```
93
+
94
+ ## What `transaction` is
95
+
96
+ The sponsor methods take a `transaction` that is anything `Transaction.from` accepts:
97
+
98
+ ```ts
99
+ type TransactionInput = Transaction | Uint8Array | string; // a Transaction, built bytes, base64, or JSON
100
+ ```
101
+
102
+ When you also pass a `userSignature`, the bytes are already final (the user signed them), so that
103
+ form must be the exact bytes: a `Uint8Array` or a base64 string (not a `Transaction` or JSON). The
104
+ sponsor never rebuilds them (so the user's signature stays valid). The sender is already part of
105
+ those bytes, so there's no `sender` parameter in this flow. `userSignature` might be a single string
106
+ or an array (for example, multiple required signers).
107
+
108
+ ## Next steps
109
+
110
+ - [Basic Usage](/sponsor/basic-usage) covers the recommended service flow: client-built bytes, user
111
+ signatures, network usage, and the advanced sponsor-builds flow.
112
+ - [Validation](/sponsor/validators) explains custom validation analyzers, request-scoped options,
113
+ analyzer execution, and built-in validation analyzers.
114
+ - [Best Practices](/sponsor/best-practices) adds policy, gas, and operational guidance.
@@ -0,0 +1,7 @@
1
+ # Sponsor
2
+ > Build gas sponsorship services with pluggable validation
3
+
4
+ - [Sponsor](..md): Build gas sponsorship services with pluggable transaction validation
5
+ - [Basic Usage](./basic-usage.md): Build a service that validates and executes user-signed sponsored bytes
6
+ - [Validation](./validators.md): Compose built-in and custom validation analyzers for sponsorship policy
7
+ - [Best Practices](./best-practices.md): Operate a Sui sponsor with clear policy, gas limits, and outcome handling