From f066b632e135b7ec01ade4c41f65edfd814f4f6e Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Sep 2026 12:25:18 +0100 Subject: [PATCH 1/2] feat: align Stellar SDK with mainnet readiness --- CHANGELOG.md | 22 ++ README.md | 14 +- docs/SUMMARY.md | 3 +- docs/api-reference.md | 32 ++- docs/configuration.md | 10 +- docs/generated-bindings.md | 18 ++ docs/router-paymaster.md | 6 +- docs/sponsorship.md | 38 +++ package.json | 3 +- src/core/errors.ts | 3 + src/core/types.ts | 40 +++ src/generated/flow/src/index.ts | 97 +++++-- src/generated/lockup/src/index.ts | 154 +++++++----- src/generated/router/src/index.ts | 161 ++++++++++-- src/generated/stream_nft/src/index.ts | 120 ++++++--- src/index.ts | 2 +- src/stellar/client.test.ts | 44 +++- src/stellar/client.ts | 53 ++++ src/stellar/index.ts | 2 + src/stellar/lockup-client.ts | 113 +++++++++ src/stellar/paymaster-client.ts | 1 + src/stellar/protocol-clients.test.ts | 88 +++++++ src/stellar/router-client.ts | 91 ++++++- src/stellar/sponsorship-client.test.ts | 160 ++++++++++++ src/stellar/sponsorship-client.ts | 336 +++++++++++++++++++++++++ src/stellar/stream-nft-client.ts | 15 ++ src/stellar/types.ts | 2 + 27 files changed, 1484 insertions(+), 144 deletions(-) create mode 100644 docs/sponsorship.md create mode 100644 src/stellar/lockup-client.ts create mode 100644 src/stellar/sponsorship-client.test.ts create mode 100644 src/stellar/sponsorship-client.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e8236ab..ca19b9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ All notable changes to `@fundable/sdk` are documented here. +## 0.2.0 + +Mainnet-readiness integration release. + +### Added + +- authenticated Stellar wallet sessions for the Fundable backend; +- typed sponsorship quote, build, authorization signing, and idempotent submit; +- explicit sponsorship fee and authorization-expiration fields; +- canonical Router stream metadata, status, owner, and core-ID reads; +- first-class Lockup cancellation, status, and amount reads; +- immutable Stream NFT transferability reads; +- Router Flow initial funding and per-stream transferability inputs. + +### Changed + +- regenerated Flow, Lockup, Router, and Stream NFT bindings from the verified + mainnet-readiness release artifacts; +- distinguished confirmed Stream NFT token IDs from transaction hashes in + sponsorship results; +- marked the custom Fundable Paymaster client as deprecated and migration-only. + ## 0.1.0 First stable Fundable SDK release. diff --git a/README.md b/README.md index 7ab1057..c565927 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Detailed, GitBook-ready guides live in [`docs/`](./docs): - [Configuration](./docs/configuration.md) - [Flow streams](./docs/flows.md) - [Transactions and authorization](./docs/transactions.md) +- [Sponsored transactions](./docs/sponsorship.md) - [Example implementations](./docs/examples.md) - [Backend indexing](./docs/backend-indexing.md) - [Multichain architecture](./docs/multichain-architecture.md) @@ -41,13 +42,12 @@ const fundable = createFundableClient({ network: "testnet", rpcUrl: "https://soroban-testnet.stellar.org", networkPassphrase: "Test SDF Network ; September 2015", - contracts: { - flow: "C...", - }, + contracts: { flow: "C...", router: "C...", streamNft: "C..." }, + sponsorship: { backendUrl: "https://api.fundable.finance" }, }); ``` -The top-level client exposes Flow, Router, Stream NFT, and Paymaster capability +The top-level client exposes Flow, Router, Stream NFT, and sponsorship capability groups when their contract IDs are configured: ```ts @@ -88,8 +88,10 @@ console.log(sent.result); ``` Fundable's NFT-backed creation workflow goes through `fundable.router`. The -`fundable.streamNft` and `fundable.paymaster` groups expose NFT ownership and -bounded fee-forwarding operations. See the capability guide for examples. +`fundable.streamNft` group exposes NFT ownership and transfer policy. +`fundable.sponsorship` implements the production backend-mediated fee flow. +The legacy `fundable.paymaster` group is deprecated and retained only for +migration compatibility. ## Multichain boundary diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 2cfd525..8a6e242 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,10 +5,11 @@ - [Configuration](configuration.md) - [Flow streams](flows.md) - [Transactions and authorization](transactions.md) +- [Sponsored transactions](sponsorship.md) - [Example implementations](examples.md) - [Backend indexing](backend-indexing.md) - [Multichain architecture](multichain-architecture.md) -- [Router, Stream NFT, and Paymaster](router-paymaster.md) +- [Router, Stream NFT, and legacy Paymaster](router-paymaster.md) - [API reference](api-reference.md) - [Generated Stellar bindings](generated-bindings.md) - [Troubleshooting and versioning](troubleshooting-and-versioning.md) diff --git a/docs/api-reference.md b/docs/api-reference.md index 10e3782..177b6a2 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -16,9 +16,11 @@ import type { StellarFundableClientConfig } from "@fundable/sdk/stellar"; | Property | Availability | | --- | --- | | `flows` | Always present; `contracts.flow` is required. | +| `lockups` | Present when `contracts.lockup` is configured. | | `router` | Present when `contracts.router` is configured. | | `streamNft` | Present when `contracts.streamNft` is configured. | -| `paymaster` | Present when `contracts.paymaster` is configured. | +| `sponsorship` | Present when `sponsorship.backendUrl` is configured. | +| `paymaster` | Deprecated; migration-only when `contracts.paymaster` is configured. | ## Flow client @@ -40,6 +42,17 @@ import type { StellarFundableClientConfig } from "@fundable/sdk/stellar"; | `createLockup` | Assembled transaction returning Stream NFT token ID. | | `withdraw` | Assembled transaction for an amount and token ID. | | `withdrawMax` | Assembled transaction returning the withdrawn amount. | +| `ownerOf`, `statusOf`, `coreStreamId` | Canonical reads keyed by NFT token ID. | +| `getStream` | Owner, type, status, transferability, token ID, and core ID. | +| `voidFlow` | NFT-owner Flow void transaction. | + +## Lockup client + +| Method | Result | +| --- | --- | +| `cancel`, `renounce` | Sender-authorized Lockup transactions using a core stream ID. | +| `statusOf`, `isCancelable` | Current Lockup lifecycle and cancellation state. | +| `withdrawableAmount`, `refundableAmount` | Current amounts as `bigint`. | ## Stream NFT client @@ -48,8 +61,22 @@ import type { StellarFundableClientConfig } from "@fundable/sdk/stellar"; | `ownerOf` | Owner Stellar address. | | `balanceOf` | Number of Stream NFTs owned as `bigint`. | | `getStreamData` | `{ tokenId, streamId, streamKind }`. | +| `isTransferable` | Immutable on-chain transfer policy. | | `transfer` | Assembled transfer transaction. | +## Sponsorship client + +| Method | Result | +| --- | --- | +| `authenticate` | Signed-wallet backend session. | +| `quote` | Fee token, estimate, maximum, and conversion rate. | +| `build` | FeeForwarder transaction, user auth entry, and expiration. | +| `submit` | Durable IDs, status, transaction hash, and confirmed NFT token ID. | +| `isBuildExpired` | Whether a build must be rebuilt before signing or submission. | + +`fundable.signSponsorshipAuthorization(build)` signs the backend-issued user +authorization entry with the configured wallet callback. + ## Paymaster client | Method | Result | @@ -89,6 +116,7 @@ try { The stable error codes are `INVALID_ARGUMENT`, `INVALID_CONFIGURATION`, `UNSUPPORTED_CHAIN`, `UNSUPPORTED_CAPABILITY`, `SIMULATION_FAILED`, -`TRANSACTION_FAILED`, and `RPC_UNAVAILABLE`. The underlying failure may be +`TRANSACTION_FAILED`, `RPC_UNAVAILABLE`, `API_REQUEST_FAILED`, +`AUTHENTICATION_REQUIRED`, and `AUTHORIZATION_EXPIRED`. The underlying failure may be available as `error.cause`; do not display raw provider responses to end users without filtering sensitive data. diff --git a/docs/configuration.md b/docs/configuration.md index a6e6447..37afde3 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -15,8 +15,8 @@ const fundable = createFundableClient({ flow: process.env.FUNDABLE_FLOW_CONTRACT!, router: process.env.FUNDABLE_ROUTER_CONTRACT, streamNft: process.env.FUNDABLE_STREAM_NFT_CONTRACT, - paymaster: process.env.FUNDABLE_PAYMASTER_CONTRACT, }, + sponsorship: { backendUrl: process.env.FUNDABLE_BACKEND_URL! }, publicKey: connectedAccount, signTransaction: wallet.signTransaction, signAuthEntry: wallet.signAuthEntry, @@ -39,8 +39,8 @@ cross-network signing errors. ## Optional capabilities -`router`, `streamNft`, and `paymaster` are only present on the client when the -matching contract ID is configured: +`router`, `streamNft`, and `sponsorship` are only present on the client when +their corresponding contract ID or backend URL is configured: ```ts if (!fundable.router) { @@ -51,6 +51,10 @@ if (!fundable.router) { This makes a missing deployment explicit instead of sending a transaction to an invented or stale address. +The custom `paymaster` contract option is deprecated and must not be used for +production sponsorship. Configure `sponsorship.backendUrl` to use the +backend-validated OpenZeppelin FeeForwarder flow. + ## Signing callbacks `publicKey`, `signTransaction`, and `signAuthEntry` are optional for read-only diff --git a/docs/generated-bindings.md b/docs/generated-bindings.md index 9642f41..9d5cd85 100644 --- a/docs/generated-bindings.md +++ b/docs/generated-bindings.md @@ -70,3 +70,21 @@ bindings are compiled into the SDK behind chain-neutral high-level clients. `0.1.0-alpha.3` adds the high-level Lockup Router mapping without changing the generated bindings or contract artifacts, so it retains this exact provenance. + +## Mainnet-readiness release provenance + +- Contract commit: `62dfc41ac8c1fa48d66c5fe5e9c7e98f9e4f9529` +- Stellar CLI: `stellar 27.0.0` + +Reproducible WASM SHA-256 values: + +| Contract | SHA-256 | +| --- | --- | +| Flow | `6201c10afc509b53bf3a9bb307730ee996accdcc4f17864126bbd8aa576dbee6` | +| Lockup | `500f152a8ffb8126e36af6df149eee5712c9a575b9c1979142de2619fe8920d3` | +| Router | `c1ddde9e7679fd1068523a7994a7fbc1390a0a98526ccfba0a317886f36b6f09` | +| Stream NFT | `0e6440a0a2108b254459988c214bcb225ddd838fabdb7623450a65ccbdeddb63` | + +The SDK `0.2.0` bindings were generated from the four reproducible release +artifacts with the corresponding `pnpm generate:* -- /absolute/path.wasm` +commands. The transitional Paymaster was intentionally not regenerated. diff --git a/docs/router-paymaster.md b/docs/router-paymaster.md index 1005f78..3e49fb6 100644 --- a/docs/router-paymaster.md +++ b/docs/router-paymaster.md @@ -1,4 +1,8 @@ -# Router, Stream NFT, and Paymaster development +# Router, Stream NFT, and legacy Paymaster development + +> The custom Fundable Paymaster is deprecated and retained for migration only. +> Production applications must use the backend-mediated sponsorship API +> documented in [Sponsored transactions](sponsorship.md). These capabilities are available from `0.1.0-alpha.2`. Router Lockup creation is available from `0.1.0-alpha.3`. diff --git a/docs/sponsorship.md b/docs/sponsorship.md new file mode 100644 index 0000000..8caba86 --- /dev/null +++ b/docs/sponsorship.md @@ -0,0 +1,38 @@ +# Sponsored transactions + +Production sponsorship uses Fundable's authenticated backend and the native +OpenZeppelin FeeForwarder flow. The browser prepares the intended Router or +engine call; the backend chooses and validates the fee token, relayer, target, +function, arguments, fee bounds, and authorization tree. + +```ts +await fundable.sponsorship!.authenticate( + connectedAccount, + "TESTNET", + wallet.signMessage, +); + +const unsignedXdr = transaction.toXDR(); +const quote = await fundable.sponsorship!.quote(unsignedXdr, "TESTNET"); +// Display quote.feeToken, estimatedFeeUi, maximumFeeUi, and the action. + +let build = await fundable.sponsorship!.build(unsignedXdr, "TESTNET"); +if (fundable.sponsorship!.isBuildExpired(build)) { + build = await fundable.sponsorship!.build(unsignedXdr, "TESTNET"); +} + +const signedAuthEntry = + await fundable.signSponsorshipAuthorization(build); +const result = await fundable.sponsorship!.submit({ + build, + signedAuthEntry, + network: "TESTNET", + intent, + idempotencyKey: crypto.randomUUID(), +}); +``` + +Use `result.streamTokenId` as the public stream identifier only after it is +present in a confirmed backend result. `result.transactionHash` identifies the +submission and must never be substituted for a stream ID. If authorization +expires, rebuild and show the updated fee before asking the wallet to sign. diff --git a/package.json b/package.json index 1a3f6f8..e98afbd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fundable/sdk", - "version": "0.1.0", + "version": "0.2.0", "private": false, "description": "Multichain TypeScript SDK for Fundable Protocol; Stellar adapter included", "license": "MIT", @@ -46,6 +46,7 @@ "build": "pnpm clean && tsc", "dev": "tsc --watch", "generate:flow": "node scripts/generate-stellar-binding.mjs flow", + "generate:lockup": "node scripts/generate-stellar-binding.mjs lockup", "generate:router": "node scripts/generate-stellar-binding.mjs router", "generate:stream-nft": "node scripts/generate-stellar-binding.mjs stream_nft", "generate:paymaster": "node scripts/generate-stellar-binding.mjs paymaster", diff --git a/src/core/errors.ts b/src/core/errors.ts index c89c610..0987b53 100644 --- a/src/core/errors.ts +++ b/src/core/errors.ts @@ -8,6 +8,9 @@ export const FUNDABLE_ERROR_CODES = { SIMULATION_FAILED: "SIMULATION_FAILED", TRANSACTION_FAILED: "TRANSACTION_FAILED", RPC_UNAVAILABLE: "RPC_UNAVAILABLE", + API_REQUEST_FAILED: "API_REQUEST_FAILED", + AUTHENTICATION_REQUIRED: "AUTHENTICATION_REQUIRED", + AUTHORIZATION_EXPIRED: "AUTHORIZATION_EXPIRED", } as const; export type FundableErrorCode = diff --git a/src/core/types.ts b/src/core/types.ts index 0cf56d1..530c338 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -18,6 +18,17 @@ export const FLOW_STATUSES = { export type FlowStatus = (typeof FLOW_STATUSES)[keyof typeof FLOW_STATUSES]; +export const LOCKUP_STATUSES = { + PENDING: "pending", + STREAMING: "streaming", + SETTLED: "settled", + CANCELED: "canceled", + DEPLETED: "depleted", +} as const; + +export type LockupStatus = + (typeof LOCKUP_STATUSES)[keyof typeof LOCKUP_STATUSES]; + export interface TokenReference { address: string; decimals: number; @@ -43,6 +54,8 @@ export interface CreateFlowInput { token: TokenReference; ratePerSecond: bigint; startTime?: bigint | Date; + initialAmount?: bigint; + transferable?: boolean; } export interface CreateAndDepositFlowInput extends CreateFlowInput { @@ -61,6 +74,7 @@ export interface CreateLockupInput { cliffUnlockAmount?: bigint; granularitySeconds?: bigint; cancelable?: boolean; + transferable?: boolean; } export interface FlowAmountInput { @@ -97,6 +111,27 @@ export const STREAM_KINDS = { export type StreamKind = (typeof STREAM_KINDS)[keyof typeof STREAM_KINDS]; +export const CANONICAL_STREAM_STATUSES = { + PENDING: "pending", + ACTIVE: "active", + PAUSED: "paused", + CANCELED: "canceled", + COMPLETED: "completed", + FAILED: "failed", +} as const; + +export type CanonicalStreamStatus = + (typeof CANONICAL_STREAM_STATUSES)[keyof typeof CANONICAL_STREAM_STATUSES]; + +export interface StreamMetadata { + tokenId: string; + coreStreamId: string; + streamKind: StreamKind; + status: CanonicalStreamStatus; + owner: string; + transferable: boolean; +} + export interface RouterWithdrawInput { tokenId: string | bigint; caller: string; @@ -104,6 +139,11 @@ export interface RouterWithdrawInput { amount: bigint; } +export interface LockupActorInput { + streamId: string | bigint; + sender: string; +} + export interface StreamNftRecord { tokenId: string; streamId: string; diff --git a/src/generated/flow/src/index.ts b/src/generated/flow/src/index.ts index 3d6d612..5cbefc7 100644 --- a/src/generated/flow/src/index.ts +++ b/src/generated/flow/src/index.ts @@ -27,7 +27,7 @@ export * as rpc from "@stellar/stellar-sdk/rpc"; /** * Core data structure for a Flow (open-ended, rate-per-second) stream. - * + * * Ported from Sablier's `Flow.Stream` struct with these adaptations: * * - `UD21x18 ratePerSecond` → `i128 rate_per_second` scaled to 18 decimals. @@ -219,6 +219,37 @@ export enum StreamStatus { } +/** + * Stable public metadata for a Stream NFT and its underlying core stream. + */ +export interface StreamMetadata { + /** + * Internal identifier assigned by the selected core engine. + */ +core_stream_id: u64; + /** + * Current holder of withdrawal and NFT-owner Flow void rights. + */ +owner: string; + /** + * Canonical cross-engine lifecycle state. + */ +status: CanonicalStreamStatus; + /** + * Core engine kind. + */ +stream_type: StreamType; + /** + * Public, deployment-scoped stream identifier. + */ +token_id: i128; + /** + * Immutable transferability policy selected when the stream is created. + */ +transferable: boolean; +} + + /** * Parameters for creating a new Lockup stream. * @@ -272,6 +303,21 @@ token: string; total_amount: i128; } +/** + * Canonical lifecycle exposed by the Router to every product surface. + * + * Core engines retain their more detailed accounting states; the Router maps + * those states into this stable cross-engine vocabulary. + */ +export enum CanonicalStreamStatus { + Pending = 0, + Active = 1, + Paused = 2, + Canceled = 3, + Completed = 4, + Failed = 5, +} + /** * Errors specific to the Stream NFT contract. */ @@ -373,7 +419,11 @@ export const FlowError = { /** * Rate per second must not be negative. */ - 21: {message:"NegativeRate"} + 21: {message:"NegativeRate"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 22: {message:"TokenTransferMismatch"} } /** @@ -419,7 +469,15 @@ export const LockupError = { /** * Sender and recipient must be different addresses. */ - 110: {message:"SenderEqualsRecipient"} + 110: {message:"SenderEqualsRecipient"}, + /** + * Start and cliff unlock amounts must be nonnegative and fit in total amount. + */ + 111: {message:"InvalidUnlockAmount"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 112: {message:"TokenTransferMismatch"} } /** @@ -429,7 +487,11 @@ export const RouterError = { 301: {message:"AlreadyInitialized"}, 302: {message:"NotInitialized"}, 303: {message:"NotAuthorized"}, - 304: {message:"InvalidStreamType"} + 304: {message:"InvalidStreamType"}, + /** + * Core contract addresses were already configured. + */ + 305: {message:"AlreadyConfigured"} } /** @@ -438,7 +500,7 @@ export const RouterError = { * Using a typed enum prevents key collisions (SKILL.md §3). * Keys are namespaced by variant to keep different data types separate. */ -export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "AllowedFeeTokens", values: void}; +export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "TokenTransferable", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "Router", values: void} | {tag: "AllowedFeeTokens", values: void}; export interface Client { /** @@ -531,14 +593,6 @@ export interface Client { */ get_stream: ({stream_id}: {stream_id: u64}, options?: MethodOptions) => Promise> - /** - * Construct and simulate a initialize transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Initialize the contract with an admin address. - * - * Must be called exactly once before any other function. - */ - initialize: ({admin}: {admin: string}, options?: MethodOptions) => Promise> - /** * Construct and simulate a refund_max transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Refund the maximum refundable amount. @@ -631,6 +685,8 @@ export interface Client { } export class Client extends ContractClient { static async deploy( + /** Constructor/Initialization Args for the contract's `__constructor` method */ + {admin}: {admin: string}, /** Options for initializing a Client as well as for calling a method, with extras specific to deploying. */ options: MethodOptions & Omit & { @@ -642,7 +698,7 @@ export class Client extends ContractClient { format?: "hex" | "base64"; } ): Promise> { - return ContractClient.deploy(null, options) + return ContractClient.deploy({admin}, options) } constructor(public readonly options: ContractClientOptions) { super( @@ -656,12 +712,12 @@ export class Client extends ContractClient { "AAAAAAAAADRUcmFuc2ZlciBhZG1pbiByaWdodHMgdG8gYSBuZXcgYWRkcmVzcy4KCkFkbWluLW9ubHkuAAAACXNldF9hZG1pbgAAAAAAAAEAAAAAAAAACW5ld19hZG1pbgAAAAAAABMAAAAA", "AAAAAAAAACBHZXQgdGhlIHN0cmVhbSdzIGN1cnJlbnQgc3RhdHVzLgAAAAlzdGF0dXNfb2YAAAAAAAABAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAQAAB9AAAAAMU3RyZWFtU3RhdHVz", "AAAAAAAAABtHZXQgdGhlIGZ1bGwgc3RyZWFtIHJlY29yZC4AAAAACmdldF9zdHJlYW0AAAAAAAEAAAAAAAAACXN0cmVhbV9pZAAAAAAAAAYAAAABAAAH0AAAAApGbG93U3RyZWFtAAA=", - "AAAAAAAAAGZJbml0aWFsaXplIHRoZSBjb250cmFjdCB3aXRoIGFuIGFkbWluIGFkZHJlc3MuCgpNdXN0IGJlIGNhbGxlZCBleGFjdGx5IG9uY2UgYmVmb3JlIGFueSBvdGhlciBmdW5jdGlvbi4AAAAAAAppbml0aWFsaXplAAAAAAABAAAAAAAAAAVhZG1pbgAAAAAAABMAAAAA", "AAAAAAAAAFBSZWZ1bmQgdGhlIG1heGltdW0gcmVmdW5kYWJsZSBhbW91bnQuCgpTZW5kZXItb25seS4gUmV0dXJucyB0aGUgYW1vdW50IHJlZnVuZGVkLgAAAApyZWZ1bmRfbWF4AAAAAAACAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAAAAAAZzZW5kZXIAAAAAABMAAAABAAAACw==", "AAAAAAAAAHhBZGp1c3QgdGhlIHJhdGUgcGVyIHNlY29uZCBvZiBhbiBhY3RpdmUgc3RyZWFtLgoKU2VuZGVyLW9ubHkuIFRoZSBzdHJlYW0gbXVzdCBiZSBhY3RpdmVseSBzdHJlYW1pbmcgKG5vdCBwYXVzZWQvdm9pZGVkKS4AAAALYWRqdXN0X3JhdGUAAAAAAwAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAAAAAAGc2VuZGVyAAAAAAATAAAAAAAAAAhuZXdfcmF0ZQAAAAsAAAAA", "AAAAAAAAACFHZXQgdGhlIHN0cmVhbSdzIGN1cnJlbnQgYmFsYW5jZS4AAAAAAAALZ2V0X2JhbGFuY2UAAAAAAQAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAEAAAAL", "AAAAAAAAAIRQZXJtYW5lbnRseSB2b2lkIGEgc3RyZWFtLgoKQ2FsbGFibGUgYnkgc2VuZGVyIE9SIHJlY2lwaWVudC4gV3JpdGVzIG9mZiB1bmNvdmVyZWQgZGVidCBhbmQKcHJldmVudHMgdGhlIHN0cmVhbSBmcm9tIGJlaW5nIHJlc3RhcnRlZC4AAAALdm9pZF9zdHJlYW0AAAAAAgAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAAAAAAGY2FsbGVyAAAAAAATAAAAAA==", "AAAAAAAAAHFXaXRoZHJhdyB0aGUgbWF4aW11bSBhdmFpbGFibGUgYW1vdW50IGZyb20gYSBzdHJlYW0uCgpDb252ZW5pZW5jZSBmdW5jdGlvbiDigJQgd2l0aGRyYXdzIHRoZSBlbnRpcmUgY292ZXJlZCBkZWJ0LgAAAAAAAAx3aXRoZHJhd19tYXgAAAADAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAAAAAAZjYWxsZXIAAAAAABMAAAAAAAAAAnRvAAAAAAATAAAAAQAAAAs=", + "AAAAAAAAADVBdG9taWNhbGx5IGluaXRpYWxpemUgdGhlIGNvbnRyYWN0IGR1cmluZyBkZXBsb3ltZW50LgAAAAAAAA1fX2NvbnN0cnVjdG9yAAAAAAAAAQAAAAAAAAAFYWRtaW4AAAAAAAATAAAAAA==", "AAAAAAAAAC1HZXQgdGhlIHRvdGFsIGRlYnQgb3dlZCAobWF5IGV4Y2VlZCBiYWxhbmNlKS4AAAAAAAANdG90YWxfZGVidF9vZgAAAAAAAAEAAAAAAAAACXN0cmVhbV9pZAAAAAAAAAYAAAABAAAACw==", "AAAAAAAAAC5HZXQgdGhlIGNvdmVyZWQgZGVidCAoZGVidCBiYWNrZWQgYnkgYmFsYW5jZSkuAAAAAAAPY292ZXJlZF9kZWJ0X29mAAAAAAEAAAAAAAAACXN0cmVhbV9pZAAAAAAAAAYAAAABAAAACw==", "AAAAAAAAADxHZXQgdGhlIHRpbWUgYXQgd2hpY2ggdGhlIHN0cmVhbSdzIGJhbGFuY2Ugd2lsbCBiZSBkZXBsZXRlZC4AAAARZGVwbGV0aW9uX3RpbWVfb2YAAAAAAAABAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAQAAAAY=", @@ -675,12 +731,14 @@ export class Client extends ContractClient { "AAAAAwAAAMtTdGF0dXMgb2YgYSBMb2NrdXAgc3RyZWFtLgoKVXNlcyAidGVtcGVyYXR1cmUiIHNlbWFudGljczoKLSAqKldhcm0qKiAoUGVuZGluZywgU3RyZWFtaW5nKTogdGltZSBhbG9uZSBjYW4gY2hhbmdlIHRoZSBzdGF0dXMuCi0gKipDb2xkKiogKFNldHRsZWQsIENhbmNlbGVkLCBEZXBsZXRlZCk6IHRpbWUgYWxvbmUgY2Fubm90IGNoYW5nZSB0aGUgc3RhdHVzLgAAAAAAAAAADExvY2t1cFN0YXR1cwAAAAUAAAA/Q3JlYXRlZCBidXQgc3RhcnRfdGltZSBpcyBpbiB0aGUgZnV0dXJlLiBObyB0b2tlbnMgaGF2ZSB2ZXN0ZWQuAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlIOKAlCB0b2tlbnMgYXJlIGN1cnJlbnRseSB2ZXN0aW5nIG92ZXIgdGltZS4AAAAAAAlTdHJlYW1pbmcAAAAAAAABAAAAS0FsbCB0b2tlbnMgaGF2ZSBmdWxseSB2ZXN0ZWQuIFJlY2lwaWVudCBjYW4gd2l0aGRyYXcgdGhlIHJlbWFpbmluZyBiYWxhbmNlLgAAAAAHU2V0dGxlZAAAAAACAAAAP1NlbmRlciBjYW5jZWxlZCB0aGUgc3RyZWFtLiBVbnZlc3RlZCB0b2tlbnMgcmV0dXJuZWQgdG8gc2VuZGVyLgAAAAAIQ2FuY2VsZWQAAAADAAAAQkZ1bGx5IHdpdGhkcmF3biAoYW5kL29yIHJlZnVuZGVkKS4gTm8gdG9rZW5zIHJlbWFpbiBpbiB0aGUgc3RyZWFtLgAAAAAACERlcGxldGVkAAAABA==", "AAAAAQAAAtxDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIExvY2t1cCAoZml4ZWQtdGVybSB2ZXN0aW5nKSBzdHJlYW0uCgpTdXBwb3J0cyBsaW5lYXIgdW5sb2NrIHdpdGggb3B0aW9uYWwgY2xpZmYuIFRoZSB2ZXN0ZWQgKCJzdHJlYW1lZCIpIGFtb3VudAphdCBhbnkgdGltZSBgdGAgaXMgY2FsY3VsYXRlZCBhczoKCmBgYHRleHQKaWYgdCA8IHN0YXJ0X3RpbWU6ICAgICAgIHZlc3RlZCA9IDAKaWYgdCA8IGNsaWZmX3RpbWU6ICAgICAgIHZlc3RlZCA9IHN0YXJ0X3VubG9ja19hbW91bnQKaWYgdCA+PSBlbmRfdGltZTogICAgICAgICB2ZXN0ZWQgPSB0b3RhbF9hbW91bnQKZWxzZToKZWxhcHNlZCA9IGZsb29yKCh0IC0gY2xpZmZfdGltZSkgLyBncmFudWxhcml0eSkgKiBncmFudWxhcml0eQpzdHJlYW1hYmxlX2R1cmF0aW9uID0gZW5kX3RpbWUgLSBjbGlmZl90aW1lCnN0cmVhbWFibGVfYW1vdW50ID0gdG90YWxfYW1vdW50IC0gc3RhcnRfdW5sb2NrX2Ftb3VudCAtIGNsaWZmX3VubG9ja19hbW91bnQKdmVzdGVkID0gc3RhcnRfdW5sb2NrX2Ftb3VudCArIGNsaWZmX3VubG9ja19hbW91bnQgKyAoZWxhcHNlZCAqIHN0cmVhbWFibGVfYW1vdW50IC8gc3RyZWFtYWJsZV9kdXJhdGlvbikKYGBgCgpUaGlzIG1pcnJvcnMgdGhlIHJlZmVyZW5jZSBsaW5lYXIgbG9ja3VwIGNhbGN1bGF0aW9uIHdpdGggZGlzY3JldGUgdW5sb2NrCnN0ZXBzIGF0IGBncmFudWxhcml0eWAtc2Vjb25kIGludGVydmFscy4AAAAAAAAADExvY2t1cFN0cmVhbQAAAA8AAABGV2hldGhlciB0aGUgc2VuZGVyIGNhbiBjYW5jZWwgdGhpcyBzdHJlYW0gYW5kIHJlY2xhaW0gdW52ZXN0ZWQgdG9rZW5zLgAAAAAACmNhbmNlbGFibGUAAAAAAAEAAABuT3B0aW9uYWwgY2xpZmYgdGltZXN0YW1wLiBObyB0b2tlbnMgYmV5b25kIGBzdGFydF91bmxvY2tfYW1vdW50YAp2ZXN0IGJlZm9yZSB0aGlzIHRpbWUuIFNldCB0byAwIGZvciBubyBjbGlmZi4AAAAAAApjbGlmZl90aW1lAAAAAAAGAAAAR0Ftb3VudCB1bmxvY2tlZCBhdCBgY2xpZmZfdGltZWAgKGluIGFkZGl0aW9uIHRvIGBzdGFydF91bmxvY2tfYW1vdW50YCkuAAAAABNjbGlmZl91bmxvY2tfYW1vdW50AAAAAAsAAAAxVW5peCB0aW1lc3RhbXAgd2hlbiB0aGUgbG9ja3VwIGlzIGZ1bGx5IHVubG9ja2VkLgAAAAAAAAhlbmRfdGltZQAAAAYAAAB9VW5sb2NrIGdyYW51bGFyaXR5IGluIHNlY29uZHMuIFRva2VucyB2ZXN0IGluIGRpc2NyZXRlIHN0ZXBzIG9mIHRoaXMKaW50ZXJ2YWwuIERlZmF1bHQgPSAxIChwZXItc2Vjb25kIHZlc3RpbmcpLiBNdXN0IGJlID4gMC4AAAAAAAALZ3JhbnVsYXJpdHkAAAAABgAAADdXaGV0aGVyIGFsbCB0b2tlbnMgaGF2ZSBiZWVuIHdpdGhkcmF3biBhbmQvb3IgcmVmdW5kZWQuAAAAAAtpc19kZXBsZXRlZAAAAAABAAAAP1RoZSBhZGRyZXNzIHRoYXQgcmVjZWl2ZXMgdG9rZW5zIGFzIHRoZXkgdW5sb2NrIChjYW4gd2l0aGRyYXcpLgAAAAAJcmVjaXBpZW50AAAAAAAAEwAAAEFBbW91bnQgcmVmdW5kZWQgdG8gc2VuZGVyIG9uIGNhbmNlbGxhdGlvbi4gWmVybyB1bmxlc3MgY2FuY2VsbGVkLgAAAAAAAA9yZWZ1bmRlZF9hbW91bnQAAAAACwAAAExUaGUgYWRkcmVzcyB0aGF0IGNyZWF0ZWQgYW5kIGZ1bmRlZCB0aGUgbG9ja3VwIChjYW4gY2FuY2VsIGlmIGBjYW5jZWxhYmxlYCkuAAAABnNlbmRlcgAAAAAAEwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHRoZSBsb2NrdXAgYmVnaW5zLgAAAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAsQW1vdW50IHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IGBzdGFydF90aW1lYC4AAAATc3RhcnRfdW5sb2NrX2Ftb3VudAAAAAALAAAAI1RoZSBTb3JvYmFuIHRva2VuIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAV0b2tlbgAAAAAAABMAAAA7VG90YWwgYW1vdW50IGRlcG9zaXRlZCBpbnRvIHRoZSBzdHJlYW0gKGluIHRva2VuIGRlY2ltYWxzKS4AAAAADHRvdGFsX2Ftb3VudAAAAAsAAAAmV2hldGhlciB0aGUgc3RyZWFtIGhhcyBiZWVuIGNhbmNlbGxlZC4AAAAAAAx3YXNfY2FuY2VsZWQAAAABAAAALUN1bXVsYXRpdmUgYW1vdW50IHdpdGhkcmF3biBieSB0aGUgcmVjaXBpZW50LgAAAAAAABB3aXRoZHJhd25fYW1vdW50AAAACw==", "AAAAAwAAALFTdGF0dXMgb2YgYSBGbG93IHN0cmVhbS4KClRoZSBzdGF0dXMgaXMgZGVyaXZlZCBmcm9tIHRoZSBzdHJlYW0ncyBjdXJyZW50IHN0YXRlIChyYXRlLCBiYWxhbmNlLCBkZWJ0LCB2b2lkZWQgZmxhZykgcmF0aGVyIHRoYW4KYmVpbmcgc3RvcmVkIGRpcmVjdGx5IOKAlCBrZWVwaW5nIHN0b3JhZ2UgbWluaW1hbC4AAAAAAAAAAAAADFN0cmVhbVN0YXR1cwAAAAYAAAA+U3RyZWFtIHNjaGVkdWxlZCB0byBzdGFydCBpbiB0aGUgZnV0dXJlIChzbmFwc2hvdF90aW1lID4gbm93KS4AAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlbHkgc3RyZWFtaW5nIHdpdGggYmFsYW5jZSBjb3ZlcmluZyBhbGwgZGVidC4AAAAAABBTdHJlYW1pbmdTb2x2ZW50AAAAAQAAADZBY3RpdmVseSBzdHJlYW1pbmcgYnV0IGRlYnQgZXhjZWVkcyBhdmFpbGFibGUgYmFsYW5jZS4AAAAAABJTdHJlYW1pbmdJbnNvbHZlbnQAAAAAAAIAAAAoUGF1c2VkIGJ5IHNlbmRlciB3aXRoIG5vIHVuY292ZXJlZCBkZWJ0LgAAAA1QYXVzZWRTb2x2ZW50AAAAAAAAAwAAACVQYXVzZWQgYnkgc2VuZGVyIHdpdGggdW5jb3ZlcmVkIGRlYnQuAAAAAAAAD1BhdXNlZEluc29sdmVudAAAAAAEAAAARVBlcm1hbmVudGx5IHN0b3BwZWQuIENhbm5vdCBiZSByZXN0YXJ0ZWQuIFVuY292ZXJlZCBkZWJ0IHdyaXR0ZW4gb2ZmLgAAAAAAAAZWb2lkZWQAAAAAAAU=", + "AAAAAQAAAEdTdGFibGUgcHVibGljIG1ldGFkYXRhIGZvciBhIFN0cmVhbSBORlQgYW5kIGl0cyB1bmRlcmx5aW5nIGNvcmUgc3RyZWFtLgAAAAAAAAAADlN0cmVhbU1ldGFkYXRhAAAAAAAGAAAAOUludGVybmFsIGlkZW50aWZpZXIgYXNzaWduZWQgYnkgdGhlIHNlbGVjdGVkIGNvcmUgZW5naW5lLgAAAAAAAA5jb3JlX3N0cmVhbV9pZAAAAAAABgAAADxDdXJyZW50IGhvbGRlciBvZiB3aXRoZHJhd2FsIGFuZCBORlQtb3duZXIgRmxvdyB2b2lkIHJpZ2h0cy4AAAAFb3duZXIAAAAAAAATAAAAJ0Nhbm9uaWNhbCBjcm9zcy1lbmdpbmUgbGlmZWN5Y2xlIHN0YXRlLgAAAAAGc3RhdHVzAAAAAAfQAAAAFUNhbm9uaWNhbFN0cmVhbVN0YXR1cwAAAAAAABFDb3JlIGVuZ2luZSBraW5kLgAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAACxQdWJsaWMsIGRlcGxveW1lbnQtc2NvcGVkIHN0cmVhbSBpZGVudGlmaWVyLgAAAAh0b2tlbl9pZAAAAAsAAABFSW1tdXRhYmxlIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgc2VsZWN0ZWQgd2hlbiB0aGUgc3RyZWFtIGlzIGNyZWF0ZWQuAAAAAAAADHRyYW5zZmVyYWJsZQAAAAE=", "AAAAAQAAAINQYXJhbWV0ZXJzIGZvciBjcmVhdGluZyBhIG5ldyBMb2NrdXAgc3RyZWFtLgoKQnVuZGxlZCBpbnRvIGEgc3RydWN0IGJlY2F1c2UgU29yb2JhbiBjb250cmFjdCBmdW5jdGlvbnMKaGF2ZSBhIG1heCBvZiAxMCBwYXJhbWV0ZXJzLgAAAAAAAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAACwAAAClXaGV0aGVyIHRoZSBzZW5kZXIgY2FuIGNhbmNlbCB0aGUgc3RyZWFtLgAAAAAAAApjYW5jZWxhYmxlAAAAAAABAAAAME9wdGlvbmFsIGNsaWZmIHRpbWVzdGFtcC4gU2V0IHRvIDAgZm9yIG5vIGNsaWZmLgAAAApjbGlmZl90aW1lAAAAAAAGAAAAKlRva2VucyB1bmxvY2tlZCBhdCBjbGlmZiAoYWRkZWQgdG8gc3RhcnQpLgAAAAAAE2NsaWZmX3VubG9ja19hbW91bnQAAAAACwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHZlc3RpbmcgY29tcGxldGVzLgAAAAAACGVuZF90aW1lAAAABgAAADFVbmxvY2sgc3RlcCBpbnRlcnZhbCBpbiBzZWNvbmRzLiAwIGRlZmF1bHRzIHRvIDEuAAAAAAAAC2dyYW51bGFyaXR5AAAAAAYAAAAgQWRkcmVzcyByZWNlaXZpbmcgdmVzdGVkIHRva2Vucy4AAAAJcmVjaXBpZW50AAAAAAAAEwAAADhBZGRyZXNzIGZ1bmRpbmcgdGhlIHN0cmVhbSAoY2FuIGNhbmNlbCBpZiBgY2FuY2VsYWJsZWApLgAAAAZzZW5kZXIAAAAAABMAAAAjVW5peCB0aW1lc3RhbXAgd2hlbiB2ZXN0aW5nIGJlZ2lucy4AAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAlVG9rZW5zIHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IHN0YXJ0LgAAAAAAABNzdGFydF91bmxvY2tfYW1vdW50AAAAAAsAAAAfU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzLgAAAAAFdG9rZW4AAAAAAAATAAAAKVRvdGFsIHRva2VucyB0byB2ZXN0IChpbiB0b2tlbiBkZWNpbWFscykuAAAAAAAADHRvdGFsX2Ftb3VudAAAAAs=", + "AAAAAwAAAMZDYW5vbmljYWwgbGlmZWN5Y2xlIGV4cG9zZWQgYnkgdGhlIFJvdXRlciB0byBldmVyeSBwcm9kdWN0IHN1cmZhY2UuCgpDb3JlIGVuZ2luZXMgcmV0YWluIHRoZWlyIG1vcmUgZGV0YWlsZWQgYWNjb3VudGluZyBzdGF0ZXM7IHRoZSBSb3V0ZXIgbWFwcwp0aG9zZSBzdGF0ZXMgaW50byB0aGlzIHN0YWJsZSBjcm9zcy1lbmdpbmUgdm9jYWJ1bGFyeS4AAAAAAAAAAAAVQ2Fub25pY2FsU3RyZWFtU3RhdHVzAAAAAAAABgAAAAAAAAAHUGVuZGluZwAAAAAAAAAAAAAAAAZBY3RpdmUAAAAAAAEAAAAAAAAABlBhdXNlZAAAAAAAAgAAAAAAAAAIQ2FuY2VsZWQAAAADAAAAAAAAAAlDb21wbGV0ZWQAAAAAAAAEAAAAAAAAAAZGYWlsZWQAAAAAAAU=", "AAAABAAAACtFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFN0cmVhbSBORlQgY29udHJhY3QuAAAAAAAAAAAITmZ0RXJyb3IAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAMkAAAAAAAAADU5vdEF1dGhvcml6ZWQAAAAAAADKAAAAAAAAAA1Ub2tlbk5vdEZvdW5kAAAAAAAAywAAAAAAAAAPTm90VHJhbnNmZXJhYmxlAAAAAMwAAAAhVG9rZW4gSUQgaGFzIGFscmVhZHkgYmVlbiBtaW50ZWQuAAAAAAAADUFscmVhZHlNaW50ZWQAAAAAAADN", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABUAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQ==", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAoAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4=", - "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAEAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEw", - "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAANAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAAA4TnVtYmVyIG9mIE5GVHMgb3duZWQgYnkgYW4gYWRkcmVzcyAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAKTmZ0QmFsYW5jZQAAAAAAAQAAABMAAAABAAAAO1Rva2VuIG1ldGFkYXRhLCBlLmcuLCBuYW1lLCBzeW1ib2wsIFVSSSAoSW5zdGFuY2Ugc3RvcmFnZSkuAAAAAA1Ub2tlbk1ldGFkYXRhAAAAAAAAAQAAABEAAAAAAAAALFJvdXRlciBjb25maWd1cmF0aW9uOiBGbG93IGNvbnRyYWN0IGFkZHJlc3MuAAAADEZsb3dDb250cmFjdAAAAAAAAAAuUm91dGVyIGNvbmZpZ3VyYXRpb246IExvY2t1cCBjb250cmFjdCBhZGRyZXNzLgAAAAAADkxvY2t1cENvbnRyYWN0AAAAAAAAAAAAK1JvdXRlciBjb25maWd1cmF0aW9uOiBORlQgY29udHJhY3QgYWRkcmVzcy4AAAAAC05mdENvbnRyYWN0AAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABYAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQAAAD5UaGUgdG9rZW4gZGlkIG5vdCBkZWJpdCBhbmQgY3JlZGl0IHRoZSBleGFjdCByZXF1ZXN0ZWQgYW1vdW50LgAAAAAAFVRva2VuVHJhbnNmZXJNaXNtYXRjaAAAAAAAABY=", + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAwAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4AAABLU3RhcnQgYW5kIGNsaWZmIHVubG9jayBhbW91bnRzIG11c3QgYmUgbm9ubmVnYXRpdmUgYW5kIGZpdCBpbiB0b3RhbCBhbW91bnQuAAAAABNJbnZhbGlkVW5sb2NrQW1vdW50AAAAAG8AAAA+VGhlIHRva2VuIGRpZCBub3QgZGViaXQgYW5kIGNyZWRpdCB0aGUgZXhhY3QgcmVxdWVzdGVkIGFtb3VudC4AAAAAABVUb2tlblRyYW5zZmVyTWlzbWF0Y2gAAAAAAABw", + "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEwAAAAMENvcmUgY29udHJhY3QgYWRkcmVzc2VzIHdlcmUgYWxyZWFkeSBjb25maWd1cmVkLgAAABFBbHJlYWR5Q29uZmlndXJlZAAAAAAAATE=", + "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAAPAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAABFSW1tdXRhYmxlIHBlci1zdHJlYW0gTkZUIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAAAAAEVRva2VuVHJhbnNmZXJhYmxlAAAAAAAAAQAAAAsAAAABAAAAOE51bWJlciBvZiBORlRzIG93bmVkIGJ5IGFuIGFkZHJlc3MgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAACk5mdEJhbGFuY2UAAAAAAAEAAAATAAAAAQAAADtUb2tlbiBtZXRhZGF0YSwgZS5nLiwgbmFtZSwgc3ltYm9sLCBVUkkgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAANVG9rZW5NZXRhZGF0YQAAAAAAAAEAAAARAAAAAAAAACxSb3V0ZXIgY29uZmlndXJhdGlvbjogRmxvdyBjb250cmFjdCBhZGRyZXNzLgAAAAxGbG93Q29udHJhY3QAAAAAAAAALlJvdXRlciBjb25maWd1cmF0aW9uOiBMb2NrdXAgY29udHJhY3QgYWRkcmVzcy4AAAAAAA5Mb2NrdXBDb250cmFjdAAAAAAAAAAAACtSb3V0ZXIgY29uZmlndXJhdGlvbjogTkZUIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAtOZnRDb250cmFjdAAAAAAAAAAAO0NvcmUtZW5naW5lIGNvbmZpZ3VyYXRpb246IHRydXN0ZWQgUm91dGVyIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAZSb3V0ZXIAAAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), options ) } @@ -695,7 +753,6 @@ export class Client extends ContractClient { set_admin: this.txFromJSON, status_of: this.txFromJSON, get_stream: this.txFromJSON, - initialize: this.txFromJSON, refund_max: this.txFromJSON, adjust_rate: this.txFromJSON, get_balance: this.txFromJSON, diff --git a/src/generated/lockup/src/index.ts b/src/generated/lockup/src/index.ts index 3510e62..1a14c73 100644 --- a/src/generated/lockup/src/index.ts +++ b/src/generated/lockup/src/index.ts @@ -25,18 +25,9 @@ export * from "@stellar/stellar-sdk"; export * as contract from "@stellar/stellar-sdk/contract"; export * as rpc from "@stellar/stellar-sdk/rpc"; -if (typeof window !== "undefined") { - //@ts-ignore Buffer exists - window.Buffer = window.Buffer || Buffer; -} - - - - - /** * Core data structure for a Flow (open-ended, rate-per-second) stream. - * + * * Ported from Sablier's `Flow.Stream` struct with these adaptations: * * - `UD21x18 ratePerSecond` → `i128 rate_per_second` scaled to 18 decimals. @@ -228,6 +219,37 @@ export enum StreamStatus { } +/** + * Stable public metadata for a Stream NFT and its underlying core stream. + */ +export interface StreamMetadata { + /** + * Internal identifier assigned by the selected core engine. + */ +core_stream_id: u64; + /** + * Current holder of withdrawal and NFT-owner Flow void rights. + */ +owner: string; + /** + * Canonical cross-engine lifecycle state. + */ +status: CanonicalStreamStatus; + /** + * Core engine kind. + */ +stream_type: StreamType; + /** + * Public, deployment-scoped stream identifier. + */ +token_id: i128; + /** + * Immutable transferability policy selected when the stream is created. + */ +transferable: boolean; +} + + /** * Parameters for creating a new Lockup stream. * @@ -281,6 +303,21 @@ token: string; total_amount: i128; } +/** + * Canonical lifecycle exposed by the Router to every product surface. + * + * Core engines retain their more detailed accounting states; the Router maps + * those states into this stable cross-engine vocabulary. + */ +export enum CanonicalStreamStatus { + Pending = 0, + Active = 1, + Paused = 2, + Canceled = 3, + Completed = 4, + Failed = 5, +} + /** * Errors specific to the Stream NFT contract. */ @@ -382,7 +419,11 @@ export const FlowError = { /** * Rate per second must not be negative. */ - 21: {message:"NegativeRate"} + 21: {message:"NegativeRate"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 22: {message:"TokenTransferMismatch"} } /** @@ -428,7 +469,15 @@ export const LockupError = { /** * Sender and recipient must be different addresses. */ - 110: {message:"SenderEqualsRecipient"} + 110: {message:"SenderEqualsRecipient"}, + /** + * Start and cliff unlock amounts must be nonnegative and fit in total amount. + */ + 111: {message:"InvalidUnlockAmount"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 112: {message:"TokenTransferMismatch"} } /** @@ -438,33 +487,11 @@ export const RouterError = { 301: {message:"AlreadyInitialized"}, 302: {message:"NotInitialized"}, 303: {message:"NotAuthorized"}, - 304: {message:"InvalidStreamType"} -} - -/** - * Errors specific to the Paymaster (FeeForwarder) contract. - */ -export const PaymasterError = { - /** - * Contract already initialized. - */ - 401: {message:"AlreadyInitialized"}, + 304: {message:"InvalidStreamType"}, /** - * Contract not yet initialized. - */ - 402: {message:"NotInitialized"}, - /** - * Caller is not the admin. + * Core contract addresses were already configured. */ - 403: {message:"NotAuthorized"}, - /** - * The provided fee token is not in the allowed list. - */ - 404: {message:"TokenNotAllowed"}, - /** - * Fee amount must be > 0. - */ - 405: {message:"FeeAmountZero"} + 305: {message:"AlreadyConfigured"} } /** @@ -473,13 +500,13 @@ export const PaymasterError = { * Using a typed enum prevents key collisions (SKILL.md §3). * Keys are namespaced by variant to keep different data types separate. */ -export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "AllowedFeeTokens", values: void}; +export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "TokenTransferable", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "Router", values: void} | {tag: "AllowedFeeTokens", values: void}; export interface Client { /** * Construct and simulate a cancel transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Cancel a stream and reclaim unvested tokens. - * + * * Sender-only. The stream must be cancelable and not already * depleted or canceled. Returns the refunded amount. */ @@ -556,14 +583,6 @@ export interface Client { */ get_stream: ({stream_id}: {stream_id: u64}, options?: MethodOptions) => Promise> - /** - * Construct and simulate a initialize transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Initialize the contract with an admin address. - * - * Must be called exactly once before any other function. - */ - initialize: ({admin}: {admin: string}, options?: MethodOptions) => Promise> - /** * Construct and simulate a withdraw_max transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Withdraw the maximum available amount from a stream. @@ -578,6 +597,23 @@ export interface Client { */ is_cancelable: ({stream_id}: {stream_id: u64}, options?: MethodOptions) => Promise> + /** + * Construct and simulate a configure_router transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Configure the trusted Router once. + * + * The Router-only creation path consumes a sender's pre-existing token + * allowance without requiring a nested user authorization. This is the + * call shape required by OpenZeppelin FeeForwarder sponsorship. + */ + configure_router: ({router}: {router: string}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a create_from_router transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Create a fully funded stream through the trusted Router using an + * allowance previously granted by the sender to this Lockup contract. + */ + create_from_router: ({params}: {params: CreateLockupParams}, options?: MethodOptions) => Promise> + /** * Construct and simulate a streamed_amount_of transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Get the total vested ("streamed") amount at the current time. @@ -617,6 +653,8 @@ export interface Client { } export class Client extends ContractClient { static async deploy( + /** Constructor/Initialization Args for the contract's `__constructor` method */ + {admin}: {admin: string}, /** Options for initializing a Client as well as for calling a method, with extras specific to deploying. */ options: MethodOptions & Omit & { @@ -628,7 +666,7 @@ export class Client extends ContractClient { format?: "hex" | "base64"; } ): Promise> { - return ContractClient.deploy(null, options) + return ContractClient.deploy({admin}, options) } constructor(public readonly options: ContractClientOptions) { super( @@ -642,9 +680,11 @@ export class Client extends ContractClient { "AAAAAAAAADRUcmFuc2ZlciBhZG1pbiByaWdodHMgdG8gYSBuZXcgYWRkcmVzcy4KCkFkbWluLW9ubHkuAAAACXNldF9hZG1pbgAAAAAAAAEAAAAAAAAACW5ld19hZG1pbgAAAAAAABMAAAAA", "AAAAAAAAACBHZXQgdGhlIHN0cmVhbSdzIGN1cnJlbnQgc3RhdHVzLgAAAAlzdGF0dXNfb2YAAAAAAAABAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAQAAB9AAAAAMTG9ja3VwU3RhdHVz", "AAAAAAAAABtHZXQgdGhlIGZ1bGwgc3RyZWFtIHJlY29yZC4AAAAACmdldF9zdHJlYW0AAAAAAAEAAAAAAAAACXN0cmVhbV9pZAAAAAAAAAYAAAABAAAH0AAAAAxMb2NrdXBTdHJlYW0=", - "AAAAAAAAAGZJbml0aWFsaXplIHRoZSBjb250cmFjdCB3aXRoIGFuIGFkbWluIGFkZHJlc3MuCgpNdXN0IGJlIGNhbGxlZCBleGFjdGx5IG9uY2UgYmVmb3JlIGFueSBvdGhlciBmdW5jdGlvbi4AAAAAAAppbml0aWFsaXplAAAAAAABAAAAAAAAAAVhZG1pbgAAAAAAABMAAAAA", "AAAAAAAAAHhXaXRoZHJhdyB0aGUgbWF4aW11bSBhdmFpbGFibGUgYW1vdW50IGZyb20gYSBzdHJlYW0uCgpDb252ZW5pZW5jZSBmdW5jdGlvbiDigJQgd2l0aGRyYXdzIHRoZSBlbnRpcmUgd2l0aGRyYXdhYmxlIGFtb3VudC4AAAAMd2l0aGRyYXdfbWF4AAAAAwAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAAAAAAGY2FsbGVyAAAAAAATAAAAAAAAAAJ0bwAAAAAAEwAAAAEAAAAL", + "AAAAAAAAADVBdG9taWNhbGx5IGluaXRpYWxpemUgdGhlIGNvbnRyYWN0IGR1cmluZyBkZXBsb3ltZW50LgAAAAAAAA1fX2NvbnN0cnVjdG9yAAAAAAAAAQAAAAAAAAAFYWRtaW4AAAAAAAATAAAAAA==", "AAAAAAAAACJDaGVjayBpZiB0aGUgc3RyZWFtIGlzIGNhbmNlbGFibGUuAAAAAAANaXNfY2FuY2VsYWJsZQAAAAAAAAEAAAAAAAAACXN0cmVhbV9pZAAAAAAAAAYAAAABAAAAAQ==", + "AAAAAAAAAOtDb25maWd1cmUgdGhlIHRydXN0ZWQgUm91dGVyIG9uY2UuCgpUaGUgUm91dGVyLW9ubHkgY3JlYXRpb24gcGF0aCBjb25zdW1lcyBhIHNlbmRlcidzIHByZS1leGlzdGluZyB0b2tlbgphbGxvd2FuY2Ugd2l0aG91dCByZXF1aXJpbmcgYSBuZXN0ZWQgdXNlciBhdXRob3JpemF0aW9uLiBUaGlzIGlzIHRoZQpjYWxsIHNoYXBlIHJlcXVpcmVkIGJ5IE9wZW5aZXBwZWxpbiBGZWVGb3J3YXJkZXIgc3BvbnNvcnNoaXAuAAAAABBjb25maWd1cmVfcm91dGVyAAAAAQAAAAAAAAAGcm91dGVyAAAAAAATAAAAAA==", + "AAAAAAAAAIRDcmVhdGUgYSBmdWxseSBmdW5kZWQgc3RyZWFtIHRocm91Z2ggdGhlIHRydXN0ZWQgUm91dGVyIHVzaW5nIGFuCmFsbG93YW5jZSBwcmV2aW91c2x5IGdyYW50ZWQgYnkgdGhlIHNlbmRlciB0byB0aGlzIExvY2t1cCBjb250cmFjdC4AAAASY3JlYXRlX2Zyb21fcm91dGVyAAAAAAABAAAAAAAAAAZwYXJhbXMAAAAAB9AAAAASQ3JlYXRlTG9ja3VwUGFyYW1zAAAAAAABAAAABg==", "AAAAAAAAAD1HZXQgdGhlIHRvdGFsIHZlc3RlZCAoInN0cmVhbWVkIikgYW1vdW50IGF0IHRoZSBjdXJyZW50IHRpbWUuAAAAAAAAEnN0cmVhbWVkX2Ftb3VudF9vZgAAAAAAAQAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAEAAAAL", "AAAAAAAAABhHZXQgdGhlIHJlZnVuZGVkIGFtb3VudC4AAAATZ2V0X3JlZnVuZGVkX2Ftb3VudAAAAAABAAAAAAAAAAlzdHJlYW1faWQAAAAAAAAGAAAAAQAAAAs=", "AAAAAAAAABlHZXQgdGhlIGRlcG9zaXRlZCBhbW91bnQuAAAAAAAAFGdldF9kZXBvc2l0ZWRfYW1vdW50AAAAAQAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAEAAAAL", @@ -656,13 +696,14 @@ export class Client extends ContractClient { "AAAAAwAAAMtTdGF0dXMgb2YgYSBMb2NrdXAgc3RyZWFtLgoKVXNlcyAidGVtcGVyYXR1cmUiIHNlbWFudGljczoKLSAqKldhcm0qKiAoUGVuZGluZywgU3RyZWFtaW5nKTogdGltZSBhbG9uZSBjYW4gY2hhbmdlIHRoZSBzdGF0dXMuCi0gKipDb2xkKiogKFNldHRsZWQsIENhbmNlbGVkLCBEZXBsZXRlZCk6IHRpbWUgYWxvbmUgY2Fubm90IGNoYW5nZSB0aGUgc3RhdHVzLgAAAAAAAAAADExvY2t1cFN0YXR1cwAAAAUAAAA/Q3JlYXRlZCBidXQgc3RhcnRfdGltZSBpcyBpbiB0aGUgZnV0dXJlLiBObyB0b2tlbnMgaGF2ZSB2ZXN0ZWQuAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlIOKAlCB0b2tlbnMgYXJlIGN1cnJlbnRseSB2ZXN0aW5nIG92ZXIgdGltZS4AAAAAAAlTdHJlYW1pbmcAAAAAAAABAAAAS0FsbCB0b2tlbnMgaGF2ZSBmdWxseSB2ZXN0ZWQuIFJlY2lwaWVudCBjYW4gd2l0aGRyYXcgdGhlIHJlbWFpbmluZyBiYWxhbmNlLgAAAAAHU2V0dGxlZAAAAAACAAAAP1NlbmRlciBjYW5jZWxlZCB0aGUgc3RyZWFtLiBVbnZlc3RlZCB0b2tlbnMgcmV0dXJuZWQgdG8gc2VuZGVyLgAAAAAIQ2FuY2VsZWQAAAADAAAAQkZ1bGx5IHdpdGhkcmF3biAoYW5kL29yIHJlZnVuZGVkKS4gTm8gdG9rZW5zIHJlbWFpbiBpbiB0aGUgc3RyZWFtLgAAAAAACERlcGxldGVkAAAABA==", "AAAAAQAAAtxDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIExvY2t1cCAoZml4ZWQtdGVybSB2ZXN0aW5nKSBzdHJlYW0uCgpTdXBwb3J0cyBsaW5lYXIgdW5sb2NrIHdpdGggb3B0aW9uYWwgY2xpZmYuIFRoZSB2ZXN0ZWQgKCJzdHJlYW1lZCIpIGFtb3VudAphdCBhbnkgdGltZSBgdGAgaXMgY2FsY3VsYXRlZCBhczoKCmBgYHRleHQKaWYgdCA8IHN0YXJ0X3RpbWU6ICAgICAgIHZlc3RlZCA9IDAKaWYgdCA8IGNsaWZmX3RpbWU6ICAgICAgIHZlc3RlZCA9IHN0YXJ0X3VubG9ja19hbW91bnQKaWYgdCA+PSBlbmRfdGltZTogICAgICAgICB2ZXN0ZWQgPSB0b3RhbF9hbW91bnQKZWxzZToKZWxhcHNlZCA9IGZsb29yKCh0IC0gY2xpZmZfdGltZSkgLyBncmFudWxhcml0eSkgKiBncmFudWxhcml0eQpzdHJlYW1hYmxlX2R1cmF0aW9uID0gZW5kX3RpbWUgLSBjbGlmZl90aW1lCnN0cmVhbWFibGVfYW1vdW50ID0gdG90YWxfYW1vdW50IC0gc3RhcnRfdW5sb2NrX2Ftb3VudCAtIGNsaWZmX3VubG9ja19hbW91bnQKdmVzdGVkID0gc3RhcnRfdW5sb2NrX2Ftb3VudCArIGNsaWZmX3VubG9ja19hbW91bnQgKyAoZWxhcHNlZCAqIHN0cmVhbWFibGVfYW1vdW50IC8gc3RyZWFtYWJsZV9kdXJhdGlvbikKYGBgCgpUaGlzIG1pcnJvcnMgdGhlIHJlZmVyZW5jZSBsaW5lYXIgbG9ja3VwIGNhbGN1bGF0aW9uIHdpdGggZGlzY3JldGUgdW5sb2NrCnN0ZXBzIGF0IGBncmFudWxhcml0eWAtc2Vjb25kIGludGVydmFscy4AAAAAAAAADExvY2t1cFN0cmVhbQAAAA8AAABGV2hldGhlciB0aGUgc2VuZGVyIGNhbiBjYW5jZWwgdGhpcyBzdHJlYW0gYW5kIHJlY2xhaW0gdW52ZXN0ZWQgdG9rZW5zLgAAAAAACmNhbmNlbGFibGUAAAAAAAEAAABuT3B0aW9uYWwgY2xpZmYgdGltZXN0YW1wLiBObyB0b2tlbnMgYmV5b25kIGBzdGFydF91bmxvY2tfYW1vdW50YAp2ZXN0IGJlZm9yZSB0aGlzIHRpbWUuIFNldCB0byAwIGZvciBubyBjbGlmZi4AAAAAAApjbGlmZl90aW1lAAAAAAAGAAAAR0Ftb3VudCB1bmxvY2tlZCBhdCBgY2xpZmZfdGltZWAgKGluIGFkZGl0aW9uIHRvIGBzdGFydF91bmxvY2tfYW1vdW50YCkuAAAAABNjbGlmZl91bmxvY2tfYW1vdW50AAAAAAsAAAAxVW5peCB0aW1lc3RhbXAgd2hlbiB0aGUgbG9ja3VwIGlzIGZ1bGx5IHVubG9ja2VkLgAAAAAAAAhlbmRfdGltZQAAAAYAAAB9VW5sb2NrIGdyYW51bGFyaXR5IGluIHNlY29uZHMuIFRva2VucyB2ZXN0IGluIGRpc2NyZXRlIHN0ZXBzIG9mIHRoaXMKaW50ZXJ2YWwuIERlZmF1bHQgPSAxIChwZXItc2Vjb25kIHZlc3RpbmcpLiBNdXN0IGJlID4gMC4AAAAAAAALZ3JhbnVsYXJpdHkAAAAABgAAADdXaGV0aGVyIGFsbCB0b2tlbnMgaGF2ZSBiZWVuIHdpdGhkcmF3biBhbmQvb3IgcmVmdW5kZWQuAAAAAAtpc19kZXBsZXRlZAAAAAABAAAAP1RoZSBhZGRyZXNzIHRoYXQgcmVjZWl2ZXMgdG9rZW5zIGFzIHRoZXkgdW5sb2NrIChjYW4gd2l0aGRyYXcpLgAAAAAJcmVjaXBpZW50AAAAAAAAEwAAAEFBbW91bnQgcmVmdW5kZWQgdG8gc2VuZGVyIG9uIGNhbmNlbGxhdGlvbi4gWmVybyB1bmxlc3MgY2FuY2VsbGVkLgAAAAAAAA9yZWZ1bmRlZF9hbW91bnQAAAAACwAAAExUaGUgYWRkcmVzcyB0aGF0IGNyZWF0ZWQgYW5kIGZ1bmRlZCB0aGUgbG9ja3VwIChjYW4gY2FuY2VsIGlmIGBjYW5jZWxhYmxlYCkuAAAABnNlbmRlcgAAAAAAEwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHRoZSBsb2NrdXAgYmVnaW5zLgAAAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAsQW1vdW50IHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IGBzdGFydF90aW1lYC4AAAATc3RhcnRfdW5sb2NrX2Ftb3VudAAAAAALAAAAI1RoZSBTb3JvYmFuIHRva2VuIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAV0b2tlbgAAAAAAABMAAAA7VG90YWwgYW1vdW50IGRlcG9zaXRlZCBpbnRvIHRoZSBzdHJlYW0gKGluIHRva2VuIGRlY2ltYWxzKS4AAAAADHRvdGFsX2Ftb3VudAAAAAsAAAAmV2hldGhlciB0aGUgc3RyZWFtIGhhcyBiZWVuIGNhbmNlbGxlZC4AAAAAAAx3YXNfY2FuY2VsZWQAAAABAAAALUN1bXVsYXRpdmUgYW1vdW50IHdpdGhkcmF3biBieSB0aGUgcmVjaXBpZW50LgAAAAAAABB3aXRoZHJhd25fYW1vdW50AAAACw==", "AAAAAwAAALFTdGF0dXMgb2YgYSBGbG93IHN0cmVhbS4KClRoZSBzdGF0dXMgaXMgZGVyaXZlZCBmcm9tIHRoZSBzdHJlYW0ncyBjdXJyZW50IHN0YXRlIChyYXRlLCBiYWxhbmNlLCBkZWJ0LCB2b2lkZWQgZmxhZykgcmF0aGVyIHRoYW4KYmVpbmcgc3RvcmVkIGRpcmVjdGx5IOKAlCBrZWVwaW5nIHN0b3JhZ2UgbWluaW1hbC4AAAAAAAAAAAAADFN0cmVhbVN0YXR1cwAAAAYAAAA+U3RyZWFtIHNjaGVkdWxlZCB0byBzdGFydCBpbiB0aGUgZnV0dXJlIChzbmFwc2hvdF90aW1lID4gbm93KS4AAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlbHkgc3RyZWFtaW5nIHdpdGggYmFsYW5jZSBjb3ZlcmluZyBhbGwgZGVidC4AAAAAABBTdHJlYW1pbmdTb2x2ZW50AAAAAQAAADZBY3RpdmVseSBzdHJlYW1pbmcgYnV0IGRlYnQgZXhjZWVkcyBhdmFpbGFibGUgYmFsYW5jZS4AAAAAABJTdHJlYW1pbmdJbnNvbHZlbnQAAAAAAAIAAAAoUGF1c2VkIGJ5IHNlbmRlciB3aXRoIG5vIHVuY292ZXJlZCBkZWJ0LgAAAA1QYXVzZWRTb2x2ZW50AAAAAAAAAwAAACVQYXVzZWQgYnkgc2VuZGVyIHdpdGggdW5jb3ZlcmVkIGRlYnQuAAAAAAAAD1BhdXNlZEluc29sdmVudAAAAAAEAAAARVBlcm1hbmVudGx5IHN0b3BwZWQuIENhbm5vdCBiZSByZXN0YXJ0ZWQuIFVuY292ZXJlZCBkZWJ0IHdyaXR0ZW4gb2ZmLgAAAAAAAAZWb2lkZWQAAAAAAAU=", + "AAAAAQAAAEdTdGFibGUgcHVibGljIG1ldGFkYXRhIGZvciBhIFN0cmVhbSBORlQgYW5kIGl0cyB1bmRlcmx5aW5nIGNvcmUgc3RyZWFtLgAAAAAAAAAADlN0cmVhbU1ldGFkYXRhAAAAAAAGAAAAOUludGVybmFsIGlkZW50aWZpZXIgYXNzaWduZWQgYnkgdGhlIHNlbGVjdGVkIGNvcmUgZW5naW5lLgAAAAAAAA5jb3JlX3N0cmVhbV9pZAAAAAAABgAAADxDdXJyZW50IGhvbGRlciBvZiB3aXRoZHJhd2FsIGFuZCBORlQtb3duZXIgRmxvdyB2b2lkIHJpZ2h0cy4AAAAFb3duZXIAAAAAAAATAAAAJ0Nhbm9uaWNhbCBjcm9zcy1lbmdpbmUgbGlmZWN5Y2xlIHN0YXRlLgAAAAAGc3RhdHVzAAAAAAfQAAAAFUNhbm9uaWNhbFN0cmVhbVN0YXR1cwAAAAAAABFDb3JlIGVuZ2luZSBraW5kLgAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAACxQdWJsaWMsIGRlcGxveW1lbnQtc2NvcGVkIHN0cmVhbSBpZGVudGlmaWVyLgAAAAh0b2tlbl9pZAAAAAsAAABFSW1tdXRhYmxlIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgc2VsZWN0ZWQgd2hlbiB0aGUgc3RyZWFtIGlzIGNyZWF0ZWQuAAAAAAAADHRyYW5zZmVyYWJsZQAAAAE=", "AAAAAQAAAINQYXJhbWV0ZXJzIGZvciBjcmVhdGluZyBhIG5ldyBMb2NrdXAgc3RyZWFtLgoKQnVuZGxlZCBpbnRvIGEgc3RydWN0IGJlY2F1c2UgU29yb2JhbiBjb250cmFjdCBmdW5jdGlvbnMKaGF2ZSBhIG1heCBvZiAxMCBwYXJhbWV0ZXJzLgAAAAAAAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAACwAAAClXaGV0aGVyIHRoZSBzZW5kZXIgY2FuIGNhbmNlbCB0aGUgc3RyZWFtLgAAAAAAAApjYW5jZWxhYmxlAAAAAAABAAAAME9wdGlvbmFsIGNsaWZmIHRpbWVzdGFtcC4gU2V0IHRvIDAgZm9yIG5vIGNsaWZmLgAAAApjbGlmZl90aW1lAAAAAAAGAAAAKlRva2VucyB1bmxvY2tlZCBhdCBjbGlmZiAoYWRkZWQgdG8gc3RhcnQpLgAAAAAAE2NsaWZmX3VubG9ja19hbW91bnQAAAAACwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHZlc3RpbmcgY29tcGxldGVzLgAAAAAACGVuZF90aW1lAAAABgAAADFVbmxvY2sgc3RlcCBpbnRlcnZhbCBpbiBzZWNvbmRzLiAwIGRlZmF1bHRzIHRvIDEuAAAAAAAAC2dyYW51bGFyaXR5AAAAAAYAAAAgQWRkcmVzcyByZWNlaXZpbmcgdmVzdGVkIHRva2Vucy4AAAAJcmVjaXBpZW50AAAAAAAAEwAAADhBZGRyZXNzIGZ1bmRpbmcgdGhlIHN0cmVhbSAoY2FuIGNhbmNlbCBpZiBgY2FuY2VsYWJsZWApLgAAAAZzZW5kZXIAAAAAABMAAAAjVW5peCB0aW1lc3RhbXAgd2hlbiB2ZXN0aW5nIGJlZ2lucy4AAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAlVG9rZW5zIHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IHN0YXJ0LgAAAAAAABNzdGFydF91bmxvY2tfYW1vdW50AAAAAAsAAAAfU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzLgAAAAAFdG9rZW4AAAAAAAATAAAAKVRvdGFsIHRva2VucyB0byB2ZXN0IChpbiB0b2tlbiBkZWNpbWFscykuAAAAAAAADHRvdGFsX2Ftb3VudAAAAAs=", + "AAAAAwAAAMZDYW5vbmljYWwgbGlmZWN5Y2xlIGV4cG9zZWQgYnkgdGhlIFJvdXRlciB0byBldmVyeSBwcm9kdWN0IHN1cmZhY2UuCgpDb3JlIGVuZ2luZXMgcmV0YWluIHRoZWlyIG1vcmUgZGV0YWlsZWQgYWNjb3VudGluZyBzdGF0ZXM7IHRoZSBSb3V0ZXIgbWFwcwp0aG9zZSBzdGF0ZXMgaW50byB0aGlzIHN0YWJsZSBjcm9zcy1lbmdpbmUgdm9jYWJ1bGFyeS4AAAAAAAAAAAAVQ2Fub25pY2FsU3RyZWFtU3RhdHVzAAAAAAAABgAAAAAAAAAHUGVuZGluZwAAAAAAAAAAAAAAAAZBY3RpdmUAAAAAAAEAAAAAAAAABlBhdXNlZAAAAAAAAgAAAAAAAAAIQ2FuY2VsZWQAAAADAAAAAAAAAAlDb21wbGV0ZWQAAAAAAAAEAAAAAAAAAAZGYWlsZWQAAAAAAAU=", "AAAABAAAACtFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFN0cmVhbSBORlQgY29udHJhY3QuAAAAAAAAAAAITmZ0RXJyb3IAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAMkAAAAAAAAADU5vdEF1dGhvcml6ZWQAAAAAAADKAAAAAAAAAA1Ub2tlbk5vdEZvdW5kAAAAAAAAywAAAAAAAAAPTm90VHJhbnNmZXJhYmxlAAAAAMwAAAAhVG9rZW4gSUQgaGFzIGFscmVhZHkgYmVlbiBtaW50ZWQuAAAAAAAADUFscmVhZHlNaW50ZWQAAAAAAADN", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABUAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQ==", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAoAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4=", - "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAEAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEw", - "AAAABAAAADlFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFBheW1hc3RlciAoRmVlRm9yd2FyZGVyKSBjb250cmFjdC4AAAAAAAAAAAAADlBheW1hc3RlckVycm9yAAAAAAAFAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAABkQAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAABkgAAABhDYWxsZXIgaXMgbm90IHRoZSBhZG1pbi4AAAANTm90QXV0aG9yaXplZAAAAAAAAZMAAAAyVGhlIHByb3ZpZGVkIGZlZSB0b2tlbiBpcyBub3QgaW4gdGhlIGFsbG93ZWQgbGlzdC4AAAAAAA9Ub2tlbk5vdEFsbG93ZWQAAAABlAAAABdGZWUgYW1vdW50IG11c3QgYmUgPiAwLgAAAAANRmVlQW1vdW50WmVybwAAAAAAAZU=", - "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAANAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAAA4TnVtYmVyIG9mIE5GVHMgb3duZWQgYnkgYW4gYWRkcmVzcyAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAKTmZ0QmFsYW5jZQAAAAAAAQAAABMAAAABAAAAO1Rva2VuIG1ldGFkYXRhLCBlLmcuLCBuYW1lLCBzeW1ib2wsIFVSSSAoSW5zdGFuY2Ugc3RvcmFnZSkuAAAAAA1Ub2tlbk1ldGFkYXRhAAAAAAAAAQAAABEAAAAAAAAALFJvdXRlciBjb25maWd1cmF0aW9uOiBGbG93IGNvbnRyYWN0IGFkZHJlc3MuAAAADEZsb3dDb250cmFjdAAAAAAAAAAuUm91dGVyIGNvbmZpZ3VyYXRpb246IExvY2t1cCBjb250cmFjdCBhZGRyZXNzLgAAAAAADkxvY2t1cENvbnRyYWN0AAAAAAAAAAAAK1JvdXRlciBjb25maWd1cmF0aW9uOiBORlQgY29udHJhY3QgYWRkcmVzcy4AAAAAC05mdENvbnRyYWN0AAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABYAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQAAAD5UaGUgdG9rZW4gZGlkIG5vdCBkZWJpdCBhbmQgY3JlZGl0IHRoZSBleGFjdCByZXF1ZXN0ZWQgYW1vdW50LgAAAAAAFVRva2VuVHJhbnNmZXJNaXNtYXRjaAAAAAAAABY=", + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAwAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4AAABLU3RhcnQgYW5kIGNsaWZmIHVubG9jayBhbW91bnRzIG11c3QgYmUgbm9ubmVnYXRpdmUgYW5kIGZpdCBpbiB0b3RhbCBhbW91bnQuAAAAABNJbnZhbGlkVW5sb2NrQW1vdW50AAAAAG8AAAA+VGhlIHRva2VuIGRpZCBub3QgZGViaXQgYW5kIGNyZWRpdCB0aGUgZXhhY3QgcmVxdWVzdGVkIGFtb3VudC4AAAAAABVUb2tlblRyYW5zZmVyTWlzbWF0Y2gAAAAAAABw", + "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEwAAAAMENvcmUgY29udHJhY3QgYWRkcmVzc2VzIHdlcmUgYWxyZWFkeSBjb25maWd1cmVkLgAAABFBbHJlYWR5Q29uZmlndXJlZAAAAAAAATE=", + "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAAPAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAABFSW1tdXRhYmxlIHBlci1zdHJlYW0gTkZUIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAAAAAEVRva2VuVHJhbnNmZXJhYmxlAAAAAAAAAQAAAAsAAAABAAAAOE51bWJlciBvZiBORlRzIG93bmVkIGJ5IGFuIGFkZHJlc3MgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAACk5mdEJhbGFuY2UAAAAAAAEAAAATAAAAAQAAADtUb2tlbiBtZXRhZGF0YSwgZS5nLiwgbmFtZSwgc3ltYm9sLCBVUkkgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAANVG9rZW5NZXRhZGF0YQAAAAAAAAEAAAARAAAAAAAAACxSb3V0ZXIgY29uZmlndXJhdGlvbjogRmxvdyBjb250cmFjdCBhZGRyZXNzLgAAAAxGbG93Q29udHJhY3QAAAAAAAAALlJvdXRlciBjb25maWd1cmF0aW9uOiBMb2NrdXAgY29udHJhY3QgYWRkcmVzcy4AAAAAAA5Mb2NrdXBDb250cmFjdAAAAAAAAAAAACtSb3V0ZXIgY29uZmlndXJhdGlvbjogTkZUIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAtOZnRDb250cmFjdAAAAAAAAAAAO0NvcmUtZW5naW5lIGNvbmZpZ3VyYXRpb246IHRydXN0ZWQgUm91dGVyIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAZSb3V0ZXIAAAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), options ) } @@ -677,9 +718,10 @@ export class Client extends ContractClient { set_admin: this.txFromJSON, status_of: this.txFromJSON, get_stream: this.txFromJSON, - initialize: this.txFromJSON, withdraw_max: this.txFromJSON, is_cancelable: this.txFromJSON, + configure_router: this.txFromJSON, + create_from_router: this.txFromJSON, streamed_amount_of: this.txFromJSON, get_refunded_amount: this.txFromJSON, get_deposited_amount: this.txFromJSON, @@ -687,4 +729,4 @@ export class Client extends ContractClient { refundable_amount_of: this.txFromJSON, withdrawable_amount_of: this.txFromJSON } -} \ No newline at end of file +} diff --git a/src/generated/router/src/index.ts b/src/generated/router/src/index.ts index ae65495..8ac9817 100644 --- a/src/generated/router/src/index.ts +++ b/src/generated/router/src/index.ts @@ -27,7 +27,7 @@ export * as rpc from "@stellar/stellar-sdk/rpc"; /** * Core data structure for a Flow (open-ended, rate-per-second) stream. - * + * * Ported from Sablier's `Flow.Stream` struct with these adaptations: * * - `UD21x18 ratePerSecond` → `i128 rate_per_second` scaled to 18 decimals. @@ -219,6 +219,37 @@ export enum StreamStatus { } +/** + * Stable public metadata for a Stream NFT and its underlying core stream. + */ +export interface StreamMetadata { + /** + * Internal identifier assigned by the selected core engine. + */ +core_stream_id: u64; + /** + * Current holder of withdrawal and NFT-owner Flow void rights. + */ +owner: string; + /** + * Canonical cross-engine lifecycle state. + */ +status: CanonicalStreamStatus; + /** + * Core engine kind. + */ +stream_type: StreamType; + /** + * Public, deployment-scoped stream identifier. + */ +token_id: i128; + /** + * Immutable transferability policy selected when the stream is created. + */ +transferable: boolean; +} + + /** * Parameters for creating a new Lockup stream. * @@ -272,6 +303,21 @@ token: string; total_amount: i128; } +/** + * Canonical lifecycle exposed by the Router to every product surface. + * + * Core engines retain their more detailed accounting states; the Router maps + * those states into this stable cross-engine vocabulary. + */ +export enum CanonicalStreamStatus { + Pending = 0, + Active = 1, + Paused = 2, + Canceled = 3, + Completed = 4, + Failed = 5, +} + /** * Errors specific to the Stream NFT contract. */ @@ -373,7 +419,11 @@ export const FlowError = { /** * Rate per second must not be negative. */ - 21: {message:"NegativeRate"} + 21: {message:"NegativeRate"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 22: {message:"TokenTransferMismatch"} } /** @@ -419,7 +469,15 @@ export const LockupError = { /** * Sender and recipient must be different addresses. */ - 110: {message:"SenderEqualsRecipient"} + 110: {message:"SenderEqualsRecipient"}, + /** + * Start and cliff unlock amounts must be nonnegative and fit in total amount. + */ + 111: {message:"InvalidUnlockAmount"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 112: {message:"TokenTransferMismatch"} } /** @@ -429,7 +487,11 @@ export const RouterError = { 301: {message:"AlreadyInitialized"}, 302: {message:"NotInitialized"}, 303: {message:"NotAuthorized"}, - 304: {message:"InvalidStreamType"} + 304: {message:"InvalidStreamType"}, + /** + * Core contract addresses were already configured. + */ + 305: {message:"AlreadyConfigured"} } /** @@ -438,7 +500,7 @@ export const RouterError = { * Using a typed enum prevents key collisions (SKILL.md §3). * Keys are namespaced by variant to keep different data types separate. */ -export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "AllowedFeeTokens", values: void}; +export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "TokenTransferable", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "Router", values: void} | {tag: "AllowedFeeTokens", values: void}; export interface Client { /** @@ -447,6 +509,12 @@ export interface Client { */ upgrade: ({new_wasm_hash}: {new_wasm_hash: Buffer}, options?: MethodOptions) => Promise> + /** + * Construct and simulate a owner_of transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return the current NFT owner for a public stream ID. + */ + owner_of: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + /** * Construct and simulate a withdraw transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Withdraw tokens from a stream using the NFT. @@ -454,10 +522,40 @@ export interface Client { withdraw: ({token_id, caller, to, amount}: {token_id: i128, caller: string, to: string, amount: i128}, options?: MethodOptions) => Promise> /** - * Construct and simulate a initialize transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Initialize the Router with the addresses of the core contracts. + * Construct and simulate a configure transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Configure core contract addresses once after all contracts are deployed. + */ + configure: ({flow_contract, lockup_contract, nft_contract}: {flow_contract: string, lockup_contract: string, nft_contract: string}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a set_admin transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Transfer Router administration to a new governance address. + */ + set_admin: ({new_admin}: {new_admin: string}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a status_of transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return the canonical cross-engine lifecycle for a public stream ID. + */ + status_of: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a void_flow transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Permanently void a Flow stream as its current NFT owner. + */ + void_flow: ({token_id, caller}: {token_id: i128, caller: string}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a get_stream transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return stable public metadata and the canonical lifecycle in one query. */ - initialize: ({admin, flow_contract, lockup_contract, nft_contract}: {admin: string, flow_contract: string, lockup_contract: string, nft_contract: string}, options?: MethodOptions) => Promise> + get_stream: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + + /** + * Construct and simulate a stream_type transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return the core engine kind for a public stream ID. + */ + stream_type: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> /** * Construct and simulate a upgrade_nft transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. @@ -471,21 +569,29 @@ export interface Client { */ withdraw_max: ({token_id, caller, to}: {token_id: i128, caller: string, to: string}, options?: MethodOptions) => Promise> + /** + * Construct and simulate a core_stream_id transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return the internal core engine ID for a public stream ID. + */ + core_stream_id: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + /** * Construct and simulate a create_flow_stream transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Create a Flow stream and mint an NFT. */ - create_flow_stream: ({sender, recipient, token, rate_per_second, token_decimals, start_time}: {sender: string, recipient: string, token: string, rate_per_second: i128, token_decimals: u32, start_time: u64}, options?: MethodOptions) => Promise> + create_flow_stream: ({sender, recipient, token, rate_per_second, token_decimals, start_time, initial_amount, transferable}: {sender: string, recipient: string, token: string, rate_per_second: i128, token_decimals: u32, start_time: u64, initial_amount: i128, transferable: boolean}, options?: MethodOptions) => Promise> /** * Construct and simulate a create_lockup_stream transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Create a Lockup stream and mint an NFT. */ - create_lockup_stream: ({params}: {params: CreateLockupParams}, options?: MethodOptions) => Promise> + create_lockup_stream: ({params, transferable}: {params: CreateLockupParams, transferable: boolean}, options?: MethodOptions) => Promise> } export class Client extends ContractClient { static async deploy( + /** Constructor/Initialization Args for the contract's `__constructor` method */ + {admin}: {admin: string}, /** Options for initializing a Client as well as for calling a method, with extras specific to deploying. */ options: MethodOptions & Omit & { @@ -497,37 +603,54 @@ export class Client extends ContractClient { format?: "hex" | "base64"; } ): Promise> { - return ContractClient.deploy(null, options) + return ContractClient.deploy({admin}, options) } constructor(public readonly options: ContractClientOptions) { super( new ContractSpec([ "AAAAAAAAACNBZG1pbiBjYW4gdXBncmFkZSB0aGUgcm91dGVyIGxvZ2ljLgAAAAAHdXBncmFkZQAAAAABAAAAAAAAAA1uZXdfd2FzbV9oYXNoAAAAAAAD7gAAACAAAAAA", + "AAAAAAAAADRSZXR1cm4gdGhlIGN1cnJlbnQgTkZUIG93bmVyIGZvciBhIHB1YmxpYyBzdHJlYW0gSUQuAAAACG93bmVyX29mAAAAAQAAAAAAAAAIdG9rZW5faWQAAAALAAAAAQAAABM=", "AAAAAAAAACxXaXRoZHJhdyB0b2tlbnMgZnJvbSBhIHN0cmVhbSB1c2luZyB0aGUgTkZULgAAAAh3aXRoZHJhdwAAAAQAAAAAAAAACHRva2VuX2lkAAAACwAAAAAAAAAGY2FsbGVyAAAAAAATAAAAAAAAAAJ0bwAAAAAAEwAAAAAAAAAGYW1vdW50AAAAAAALAAAAAA==", - "AAAAAAAAAD9Jbml0aWFsaXplIHRoZSBSb3V0ZXIgd2l0aCB0aGUgYWRkcmVzc2VzIG9mIHRoZSBjb3JlIGNvbnRyYWN0cy4AAAAACmluaXRpYWxpemUAAAAAAAQAAAAAAAAABWFkbWluAAAAAAAAEwAAAAAAAAANZmxvd19jb250cmFjdAAAAAAAABMAAAAAAAAAD2xvY2t1cF9jb250cmFjdAAAAAATAAAAAAAAAAxuZnRfY29udHJhY3QAAAATAAAAAA==", + "AAAAAAAAAEhDb25maWd1cmUgY29yZSBjb250cmFjdCBhZGRyZXNzZXMgb25jZSBhZnRlciBhbGwgY29udHJhY3RzIGFyZSBkZXBsb3llZC4AAAAJY29uZmlndXJlAAAAAAAAAwAAAAAAAAANZmxvd19jb250cmFjdAAAAAAAABMAAAAAAAAAD2xvY2t1cF9jb250cmFjdAAAAAATAAAAAAAAAAxuZnRfY29udHJhY3QAAAATAAAAAA==", + "AAAAAAAAADtUcmFuc2ZlciBSb3V0ZXIgYWRtaW5pc3RyYXRpb24gdG8gYSBuZXcgZ292ZXJuYW5jZSBhZGRyZXNzLgAAAAAJc2V0X2FkbWluAAAAAAAAAQAAAAAAAAAJbmV3X2FkbWluAAAAAAAAEwAAAAA=", + "AAAAAAAAAENSZXR1cm4gdGhlIGNhbm9uaWNhbCBjcm9zcy1lbmdpbmUgbGlmZWN5Y2xlIGZvciBhIHB1YmxpYyBzdHJlYW0gSUQuAAAAAAlzdGF0dXNfb2YAAAAAAAABAAAAAAAAAAh0b2tlbl9pZAAAAAsAAAABAAAH0AAAABVDYW5vbmljYWxTdHJlYW1TdGF0dXMAAAA=", + "AAAAAAAAADhQZXJtYW5lbnRseSB2b2lkIGEgRmxvdyBzdHJlYW0gYXMgaXRzIGN1cnJlbnQgTkZUIG93bmVyLgAAAAl2b2lkX2Zsb3cAAAAAAAACAAAAAAAAAAh0b2tlbl9pZAAAAAsAAAAAAAAABmNhbGxlcgAAAAAAEwAAAAA=", + "AAAAAAAAAEdSZXR1cm4gc3RhYmxlIHB1YmxpYyBtZXRhZGF0YSBhbmQgdGhlIGNhbm9uaWNhbCBsaWZlY3ljbGUgaW4gb25lIHF1ZXJ5LgAAAAAKZ2V0X3N0cmVhbQAAAAAAAQAAAAAAAAAIdG9rZW5faWQAAAALAAAAAQAAB9AAAAAOU3RyZWFtTWV0YWRhdGEAAA==", + "AAAAAAAAADNSZXR1cm4gdGhlIGNvcmUgZW5naW5lIGtpbmQgZm9yIGEgcHVibGljIHN0cmVhbSBJRC4AAAAAC3N0cmVhbV90eXBlAAAAAAEAAAAAAAAACHRva2VuX2lkAAAACwAAAAEAAAfQAAAAClN0cmVhbVR5cGUAAA==", "AAAAAAAAAClBZG1pbiBjYW4gdXBncmFkZSB0aGUgTkZUIGNvbnRyYWN0IGxvZ2ljLgAAAAAAAAt1cGdyYWRlX25mdAAAAAABAAAAAAAAAA1uZXdfd2FzbV9oYXNoAAAAAAAD7gAAACAAAAAA", "AAAAAAAAADBXaXRoZHJhdyBtYXggdG9rZW5zIGZyb20gYSBzdHJlYW0gdXNpbmcgdGhlIE5GVC4AAAAMd2l0aGRyYXdfbWF4AAAAAwAAAAAAAAAIdG9rZW5faWQAAAALAAAAAAAAAAZjYWxsZXIAAAAAABMAAAAAAAAAAnRvAAAAAAATAAAAAQAAAAs=", - "AAAAAAAAACVDcmVhdGUgYSBGbG93IHN0cmVhbSBhbmQgbWludCBhbiBORlQuAAAAAAAAEmNyZWF0ZV9mbG93X3N0cmVhbQAAAAAABgAAAAAAAAAGc2VuZGVyAAAAAAATAAAAAAAAAAlyZWNpcGllbnQAAAAAAAATAAAAAAAAAAV0b2tlbgAAAAAAABMAAAAAAAAAD3JhdGVfcGVyX3NlY29uZAAAAAALAAAAAAAAAA50b2tlbl9kZWNpbWFscwAAAAAABAAAAAAAAAAKc3RhcnRfdGltZQAAAAAABgAAAAEAAAAL", - "AAAAAAAAACdDcmVhdGUgYSBMb2NrdXAgc3RyZWFtIGFuZCBtaW50IGFuIE5GVC4AAAAAFGNyZWF0ZV9sb2NrdXBfc3RyZWFtAAAAAQAAAAAAAAAGcGFyYW1zAAAAAAfQAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAAAQAAAAs=", + "AAAAAAAAADlBdG9taWNhbGx5IGluaXRpYWxpemUgdGhlIFJvdXRlciBhZG1pbiBkdXJpbmcgZGVwbG95bWVudC4AAAAAAAANX19jb25zdHJ1Y3RvcgAAAAAAAAEAAAAAAAAABWFkbWluAAAAAAAAEwAAAAA=", + "AAAAAAAAADpSZXR1cm4gdGhlIGludGVybmFsIGNvcmUgZW5naW5lIElEIGZvciBhIHB1YmxpYyBzdHJlYW0gSUQuAAAAAAAOY29yZV9zdHJlYW1faWQAAAAAAAEAAAAAAAAACHRva2VuX2lkAAAACwAAAAEAAAAG", + "AAAAAAAAACVDcmVhdGUgYSBGbG93IHN0cmVhbSBhbmQgbWludCBhbiBORlQuAAAAAAAAEmNyZWF0ZV9mbG93X3N0cmVhbQAAAAAACAAAAAAAAAAGc2VuZGVyAAAAAAATAAAAAAAAAAlyZWNpcGllbnQAAAAAAAATAAAAAAAAAAV0b2tlbgAAAAAAABMAAAAAAAAAD3JhdGVfcGVyX3NlY29uZAAAAAALAAAAAAAAAA50b2tlbl9kZWNpbWFscwAAAAAABAAAAAAAAAAKc3RhcnRfdGltZQAAAAAABgAAAAAAAAAOaW5pdGlhbF9hbW91bnQAAAAAAAsAAAAAAAAADHRyYW5zZmVyYWJsZQAAAAEAAAABAAAACw==", + "AAAAAAAAACdDcmVhdGUgYSBMb2NrdXAgc3RyZWFtIGFuZCBtaW50IGFuIE5GVC4AAAAAFGNyZWF0ZV9sb2NrdXBfc3RyZWFtAAAAAgAAAAAAAAAGcGFyYW1zAAAAAAfQAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAAAAAAAAx0cmFuc2ZlcmFibGUAAAABAAAAAQAAAAs=", "AAAAAQAAA3pDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIEZsb3cgKG9wZW4tZW5kZWQsIHJhdGUtcGVyLXNlY29uZCkgc3RyZWFtLgoKUG9ydGVkIGZyb20gU2FibGllcidzIGBGbG93LlN0cmVhbWAgc3RydWN0IHdpdGggdGhlc2UgYWRhcHRhdGlvbnM6CgotIGBVRDIxeDE4IHJhdGVQZXJTZWNvbmRgIOKGkiBgaTEyOCByYXRlX3Blcl9zZWNvbmRgIHNjYWxlZCB0byAxOCBkZWNpbWFscy4KU29yb2JhbiBoYXMgbm8gbmF0aXZlIGZpeGVkLXBvaW50IHR5cGUsIHNvIHdlIHVzZSBpMTI4IHdpdGggbWFudWFsIHNjYWxpbmcuCkEgcmF0ZSBvZiAxIHRva2VuL3NlYyBmb3IgYSA3LWRlY2ltYWwgdG9rZW4gPSAxZTE4IGludGVybmFsbHkuCgotIGBJRVJDMjAgdG9rZW5gIOKGkiBgQWRkcmVzc2AgKFNvcm9iYW4gdG9rZW4gY29udHJhY3QgYWRkcmVzcykuCgotIGB1aW50MTI4IGJhbGFuY2VgIOKGkiBgaTEyOGAgKFNvcm9iYW4gU0RLIGNvbnZlbnRpb24gZm9yIHRva2VuIGFtb3VudHMpLgoKLSBgdWludDQwIHNuYXBzaG90VGltZWAg4oaSIGB1NjRgIChTb3JvYmFuIGxlZGdlciB0aW1lc3RhbXAgaXMgdTY0KS4KCi0gYGJvb2wgaXNTdHJlYW1gIHNlbnRpbmVsIHJlbW92ZWQg4oCUIHdlIHVzZSBzdG9yYWdlIGtleSBleGlzdGVuY2UgaW5zdGVhZC4KCi0gYGJvb2wgaXNUcmFuc2ZlcmFibGVgIHJlbW92ZWQg4oCUIGRlbGVnYXRlZCB0byB0aGUgTkZUIGNvbnRyYWN0IGxheWVyLgoKIyBTdG9yYWdlIExheW91dAoKRWFjaCBzdHJlYW0gaXMgc3RvcmVkIHVuZGVyIGBEYXRhS2V5OjpTdHJlYW0oc3RyZWFtX2lkKWAgaW4gcGVyc2lzdGVudCBzdG9yYWdlLgpUaGUgc3RydWN0IGlzIGtlcHQgYXMgZmxhdCBhcyBwb3NzaWJsZSB0byBtaW5pbWl6ZSBzZXJpYWxpemF0aW9uIG92ZXJoZWFkLgAAAAAAAAAAAApGbG93U3RyZWFtAAAAAAAJAAAATkN1cnJlbnQgYmFsYW5jZSBoZWxkIGluIHRoZSBzdHJlYW0gKGRlcG9zaXRlZCAtIHdpdGhkcmF3biksIGluIHRva2VuIGRlY2ltYWxzLgAAAAAAB2JhbGFuY2UAAAAACwAAAC9XaGV0aGVyIHRoZSBzdHJlYW0gaGFzIGJlZW4gcGVybWFuZW50bHkgdm9pZGVkLgAAAAAJaXNfdm9pZGVkAAAAAAAAAQAAAKVSYXRlIGF0IHdoaWNoIGRlYnQgYWNjcnVlcywgaW4gMTgtZGVjaW1hbCBmaXhlZC1wb2ludC4KMCBtZWFucyB0aGUgc3RyZWFtIGlzIHBhdXNlZC4KRXhhbXBsZTogMSB0b2tlbi9zZWMgZm9yIGEgNy1kZWNpbWFsIHRva2VuIOKGkiAxXzAwMF8wMDBfMDAwXzAwMF8wMDBfMDAwICgxZTE4KS4AAAAAAAAPcmF0ZV9wZXJfc2Vjb25kAAAAAAsAAAAwVGhlIGFkZHJlc3MgcmVjZWl2aW5nIHRoZSB0b2tlbnMgKGNhbiB3aXRoZHJhdykuAAAACXJlY2lwaWVudAAAAAAAABMAAAA9VGhlIGFkZHJlc3Mgc3RyZWFtaW5nIHRoZSB0b2tlbnMgKGNhbiBwYXVzZSwgYWRqdXN0LCByZWZ1bmQpLgAAAAAAAAZzZW5kZXIAAAAAABMAAAB6QWNjdW11bGF0ZWQgZGVidCBhdCB0aGUgbGFzdCBzbmFwc2hvdCwgaW4gMTgtZGVjaW1hbCBmaXhlZC1wb2ludC4KVG90YWwgZGVidCA9IHNuYXBzaG90X2RlYnRfc2NhbGVkICsgb25nb2luZ19kZWJ0X3NjYWxlZC4AAAAAABRzbmFwc2hvdF9kZWJ0X3NjYWxlZAAAAAsAAABkVW5peCB0aW1lc3RhbXAgb2YgdGhlIGxhc3QgZGVidCBzbmFwc2hvdC4KRGVidCBhY2NydWVzIGZyb20gdGhpcyBwb2ludCBmb3J3YXJkIGF0IGByYXRlX3Blcl9zZWNvbmRgLgAAAA1zbmFwc2hvdF90aW1lAAAAAAAABgAAADpUaGUgU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzIChTQUMgb3IgY3VzdG9tIFNFUC00MSkuAAAAAAAFdG9rZW4AAAAAAAATAAAAbU51bWJlciBvZiBkZWNpbWFscyBmb3IgdGhlIHRva2VuIChlLmcuIDcgZm9yIG1vc3QgU3RlbGxhciBhc3NldHMpLgpVc2VkIGZvciBzY2FsZS9kZXNjYWxlIG9wZXJhdGlvbnMuIE1heCAxOC4AAAAAAAAOdG9rZW5fZGVjaW1hbHMAAAAAAAQ=", "AAAAAwAAAGhUeXBlIG9mIHN0cmVhbSAoRmxvdyBvciBMb2NrdXApLgpVc2VkIGJ5IHRoZSBTdHJlYW0gTkZUIGFuZCBSb3V0ZXIgdG8gcm91dGUgbG9naWMgdG8gdGhlIGNvcnJlY3QgZW5naW5lLgAAAAAAAAAKU3RyZWFtVHlwZQAAAAAAAgAAAAAAAAAERmxvdwAAAAAAAAAAAAAABkxvY2t1cAAAAAAAAQ==", "AAAAAwAAAMtTdGF0dXMgb2YgYSBMb2NrdXAgc3RyZWFtLgoKVXNlcyAidGVtcGVyYXR1cmUiIHNlbWFudGljczoKLSAqKldhcm0qKiAoUGVuZGluZywgU3RyZWFtaW5nKTogdGltZSBhbG9uZSBjYW4gY2hhbmdlIHRoZSBzdGF0dXMuCi0gKipDb2xkKiogKFNldHRsZWQsIENhbmNlbGVkLCBEZXBsZXRlZCk6IHRpbWUgYWxvbmUgY2Fubm90IGNoYW5nZSB0aGUgc3RhdHVzLgAAAAAAAAAADExvY2t1cFN0YXR1cwAAAAUAAAA/Q3JlYXRlZCBidXQgc3RhcnRfdGltZSBpcyBpbiB0aGUgZnV0dXJlLiBObyB0b2tlbnMgaGF2ZSB2ZXN0ZWQuAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlIOKAlCB0b2tlbnMgYXJlIGN1cnJlbnRseSB2ZXN0aW5nIG92ZXIgdGltZS4AAAAAAAlTdHJlYW1pbmcAAAAAAAABAAAAS0FsbCB0b2tlbnMgaGF2ZSBmdWxseSB2ZXN0ZWQuIFJlY2lwaWVudCBjYW4gd2l0aGRyYXcgdGhlIHJlbWFpbmluZyBiYWxhbmNlLgAAAAAHU2V0dGxlZAAAAAACAAAAP1NlbmRlciBjYW5jZWxlZCB0aGUgc3RyZWFtLiBVbnZlc3RlZCB0b2tlbnMgcmV0dXJuZWQgdG8gc2VuZGVyLgAAAAAIQ2FuY2VsZWQAAAADAAAAQkZ1bGx5IHdpdGhkcmF3biAoYW5kL29yIHJlZnVuZGVkKS4gTm8gdG9rZW5zIHJlbWFpbiBpbiB0aGUgc3RyZWFtLgAAAAAACERlcGxldGVkAAAABA==", "AAAAAQAAAtxDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIExvY2t1cCAoZml4ZWQtdGVybSB2ZXN0aW5nKSBzdHJlYW0uCgpTdXBwb3J0cyBsaW5lYXIgdW5sb2NrIHdpdGggb3B0aW9uYWwgY2xpZmYuIFRoZSB2ZXN0ZWQgKCJzdHJlYW1lZCIpIGFtb3VudAphdCBhbnkgdGltZSBgdGAgaXMgY2FsY3VsYXRlZCBhczoKCmBgYHRleHQKaWYgdCA8IHN0YXJ0X3RpbWU6ICAgICAgIHZlc3RlZCA9IDAKaWYgdCA8IGNsaWZmX3RpbWU6ICAgICAgIHZlc3RlZCA9IHN0YXJ0X3VubG9ja19hbW91bnQKaWYgdCA+PSBlbmRfdGltZTogICAgICAgICB2ZXN0ZWQgPSB0b3RhbF9hbW91bnQKZWxzZToKZWxhcHNlZCA9IGZsb29yKCh0IC0gY2xpZmZfdGltZSkgLyBncmFudWxhcml0eSkgKiBncmFudWxhcml0eQpzdHJlYW1hYmxlX2R1cmF0aW9uID0gZW5kX3RpbWUgLSBjbGlmZl90aW1lCnN0cmVhbWFibGVfYW1vdW50ID0gdG90YWxfYW1vdW50IC0gc3RhcnRfdW5sb2NrX2Ftb3VudCAtIGNsaWZmX3VubG9ja19hbW91bnQKdmVzdGVkID0gc3RhcnRfdW5sb2NrX2Ftb3VudCArIGNsaWZmX3VubG9ja19hbW91bnQgKyAoZWxhcHNlZCAqIHN0cmVhbWFibGVfYW1vdW50IC8gc3RyZWFtYWJsZV9kdXJhdGlvbikKYGBgCgpUaGlzIG1pcnJvcnMgdGhlIHJlZmVyZW5jZSBsaW5lYXIgbG9ja3VwIGNhbGN1bGF0aW9uIHdpdGggZGlzY3JldGUgdW5sb2NrCnN0ZXBzIGF0IGBncmFudWxhcml0eWAtc2Vjb25kIGludGVydmFscy4AAAAAAAAADExvY2t1cFN0cmVhbQAAAA8AAABGV2hldGhlciB0aGUgc2VuZGVyIGNhbiBjYW5jZWwgdGhpcyBzdHJlYW0gYW5kIHJlY2xhaW0gdW52ZXN0ZWQgdG9rZW5zLgAAAAAACmNhbmNlbGFibGUAAAAAAAEAAABuT3B0aW9uYWwgY2xpZmYgdGltZXN0YW1wLiBObyB0b2tlbnMgYmV5b25kIGBzdGFydF91bmxvY2tfYW1vdW50YAp2ZXN0IGJlZm9yZSB0aGlzIHRpbWUuIFNldCB0byAwIGZvciBubyBjbGlmZi4AAAAAAApjbGlmZl90aW1lAAAAAAAGAAAAR0Ftb3VudCB1bmxvY2tlZCBhdCBgY2xpZmZfdGltZWAgKGluIGFkZGl0aW9uIHRvIGBzdGFydF91bmxvY2tfYW1vdW50YCkuAAAAABNjbGlmZl91bmxvY2tfYW1vdW50AAAAAAsAAAAxVW5peCB0aW1lc3RhbXAgd2hlbiB0aGUgbG9ja3VwIGlzIGZ1bGx5IHVubG9ja2VkLgAAAAAAAAhlbmRfdGltZQAAAAYAAAB9VW5sb2NrIGdyYW51bGFyaXR5IGluIHNlY29uZHMuIFRva2VucyB2ZXN0IGluIGRpc2NyZXRlIHN0ZXBzIG9mIHRoaXMKaW50ZXJ2YWwuIERlZmF1bHQgPSAxIChwZXItc2Vjb25kIHZlc3RpbmcpLiBNdXN0IGJlID4gMC4AAAAAAAALZ3JhbnVsYXJpdHkAAAAABgAAADdXaGV0aGVyIGFsbCB0b2tlbnMgaGF2ZSBiZWVuIHdpdGhkcmF3biBhbmQvb3IgcmVmdW5kZWQuAAAAAAtpc19kZXBsZXRlZAAAAAABAAAAP1RoZSBhZGRyZXNzIHRoYXQgcmVjZWl2ZXMgdG9rZW5zIGFzIHRoZXkgdW5sb2NrIChjYW4gd2l0aGRyYXcpLgAAAAAJcmVjaXBpZW50AAAAAAAAEwAAAEFBbW91bnQgcmVmdW5kZWQgdG8gc2VuZGVyIG9uIGNhbmNlbGxhdGlvbi4gWmVybyB1bmxlc3MgY2FuY2VsbGVkLgAAAAAAAA9yZWZ1bmRlZF9hbW91bnQAAAAACwAAAExUaGUgYWRkcmVzcyB0aGF0IGNyZWF0ZWQgYW5kIGZ1bmRlZCB0aGUgbG9ja3VwIChjYW4gY2FuY2VsIGlmIGBjYW5jZWxhYmxlYCkuAAAABnNlbmRlcgAAAAAAEwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHRoZSBsb2NrdXAgYmVnaW5zLgAAAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAsQW1vdW50IHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IGBzdGFydF90aW1lYC4AAAATc3RhcnRfdW5sb2NrX2Ftb3VudAAAAAALAAAAI1RoZSBTb3JvYmFuIHRva2VuIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAV0b2tlbgAAAAAAABMAAAA7VG90YWwgYW1vdW50IGRlcG9zaXRlZCBpbnRvIHRoZSBzdHJlYW0gKGluIHRva2VuIGRlY2ltYWxzKS4AAAAADHRvdGFsX2Ftb3VudAAAAAsAAAAmV2hldGhlciB0aGUgc3RyZWFtIGhhcyBiZWVuIGNhbmNlbGxlZC4AAAAAAAx3YXNfY2FuY2VsZWQAAAABAAAALUN1bXVsYXRpdmUgYW1vdW50IHdpdGhkcmF3biBieSB0aGUgcmVjaXBpZW50LgAAAAAAABB3aXRoZHJhd25fYW1vdW50AAAACw==", "AAAAAwAAALFTdGF0dXMgb2YgYSBGbG93IHN0cmVhbS4KClRoZSBzdGF0dXMgaXMgZGVyaXZlZCBmcm9tIHRoZSBzdHJlYW0ncyBjdXJyZW50IHN0YXRlIChyYXRlLCBiYWxhbmNlLCBkZWJ0LCB2b2lkZWQgZmxhZykgcmF0aGVyIHRoYW4KYmVpbmcgc3RvcmVkIGRpcmVjdGx5IOKAlCBrZWVwaW5nIHN0b3JhZ2UgbWluaW1hbC4AAAAAAAAAAAAADFN0cmVhbVN0YXR1cwAAAAYAAAA+U3RyZWFtIHNjaGVkdWxlZCB0byBzdGFydCBpbiB0aGUgZnV0dXJlIChzbmFwc2hvdF90aW1lID4gbm93KS4AAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlbHkgc3RyZWFtaW5nIHdpdGggYmFsYW5jZSBjb3ZlcmluZyBhbGwgZGVidC4AAAAAABBTdHJlYW1pbmdTb2x2ZW50AAAAAQAAADZBY3RpdmVseSBzdHJlYW1pbmcgYnV0IGRlYnQgZXhjZWVkcyBhdmFpbGFibGUgYmFsYW5jZS4AAAAAABJTdHJlYW1pbmdJbnNvbHZlbnQAAAAAAAIAAAAoUGF1c2VkIGJ5IHNlbmRlciB3aXRoIG5vIHVuY292ZXJlZCBkZWJ0LgAAAA1QYXVzZWRTb2x2ZW50AAAAAAAAAwAAACVQYXVzZWQgYnkgc2VuZGVyIHdpdGggdW5jb3ZlcmVkIGRlYnQuAAAAAAAAD1BhdXNlZEluc29sdmVudAAAAAAEAAAARVBlcm1hbmVudGx5IHN0b3BwZWQuIENhbm5vdCBiZSByZXN0YXJ0ZWQuIFVuY292ZXJlZCBkZWJ0IHdyaXR0ZW4gb2ZmLgAAAAAAAAZWb2lkZWQAAAAAAAU=", + "AAAAAQAAAEdTdGFibGUgcHVibGljIG1ldGFkYXRhIGZvciBhIFN0cmVhbSBORlQgYW5kIGl0cyB1bmRlcmx5aW5nIGNvcmUgc3RyZWFtLgAAAAAAAAAADlN0cmVhbU1ldGFkYXRhAAAAAAAGAAAAOUludGVybmFsIGlkZW50aWZpZXIgYXNzaWduZWQgYnkgdGhlIHNlbGVjdGVkIGNvcmUgZW5naW5lLgAAAAAAAA5jb3JlX3N0cmVhbV9pZAAAAAAABgAAADxDdXJyZW50IGhvbGRlciBvZiB3aXRoZHJhd2FsIGFuZCBORlQtb3duZXIgRmxvdyB2b2lkIHJpZ2h0cy4AAAAFb3duZXIAAAAAAAATAAAAJ0Nhbm9uaWNhbCBjcm9zcy1lbmdpbmUgbGlmZWN5Y2xlIHN0YXRlLgAAAAAGc3RhdHVzAAAAAAfQAAAAFUNhbm9uaWNhbFN0cmVhbVN0YXR1cwAAAAAAABFDb3JlIGVuZ2luZSBraW5kLgAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAACxQdWJsaWMsIGRlcGxveW1lbnQtc2NvcGVkIHN0cmVhbSBpZGVudGlmaWVyLgAAAAh0b2tlbl9pZAAAAAsAAABFSW1tdXRhYmxlIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgc2VsZWN0ZWQgd2hlbiB0aGUgc3RyZWFtIGlzIGNyZWF0ZWQuAAAAAAAADHRyYW5zZmVyYWJsZQAAAAE=", "AAAAAQAAAINQYXJhbWV0ZXJzIGZvciBjcmVhdGluZyBhIG5ldyBMb2NrdXAgc3RyZWFtLgoKQnVuZGxlZCBpbnRvIGEgc3RydWN0IGJlY2F1c2UgU29yb2JhbiBjb250cmFjdCBmdW5jdGlvbnMKaGF2ZSBhIG1heCBvZiAxMCBwYXJhbWV0ZXJzLgAAAAAAAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAACwAAAClXaGV0aGVyIHRoZSBzZW5kZXIgY2FuIGNhbmNlbCB0aGUgc3RyZWFtLgAAAAAAAApjYW5jZWxhYmxlAAAAAAABAAAAME9wdGlvbmFsIGNsaWZmIHRpbWVzdGFtcC4gU2V0IHRvIDAgZm9yIG5vIGNsaWZmLgAAAApjbGlmZl90aW1lAAAAAAAGAAAAKlRva2VucyB1bmxvY2tlZCBhdCBjbGlmZiAoYWRkZWQgdG8gc3RhcnQpLgAAAAAAE2NsaWZmX3VubG9ja19hbW91bnQAAAAACwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHZlc3RpbmcgY29tcGxldGVzLgAAAAAACGVuZF90aW1lAAAABgAAADFVbmxvY2sgc3RlcCBpbnRlcnZhbCBpbiBzZWNvbmRzLiAwIGRlZmF1bHRzIHRvIDEuAAAAAAAAC2dyYW51bGFyaXR5AAAAAAYAAAAgQWRkcmVzcyByZWNlaXZpbmcgdmVzdGVkIHRva2Vucy4AAAAJcmVjaXBpZW50AAAAAAAAEwAAADhBZGRyZXNzIGZ1bmRpbmcgdGhlIHN0cmVhbSAoY2FuIGNhbmNlbCBpZiBgY2FuY2VsYWJsZWApLgAAAAZzZW5kZXIAAAAAABMAAAAjVW5peCB0aW1lc3RhbXAgd2hlbiB2ZXN0aW5nIGJlZ2lucy4AAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAlVG9rZW5zIHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IHN0YXJ0LgAAAAAAABNzdGFydF91bmxvY2tfYW1vdW50AAAAAAsAAAAfU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzLgAAAAAFdG9rZW4AAAAAAAATAAAAKVRvdGFsIHRva2VucyB0byB2ZXN0IChpbiB0b2tlbiBkZWNpbWFscykuAAAAAAAADHRvdGFsX2Ftb3VudAAAAAs=", + "AAAAAwAAAMZDYW5vbmljYWwgbGlmZWN5Y2xlIGV4cG9zZWQgYnkgdGhlIFJvdXRlciB0byBldmVyeSBwcm9kdWN0IHN1cmZhY2UuCgpDb3JlIGVuZ2luZXMgcmV0YWluIHRoZWlyIG1vcmUgZGV0YWlsZWQgYWNjb3VudGluZyBzdGF0ZXM7IHRoZSBSb3V0ZXIgbWFwcwp0aG9zZSBzdGF0ZXMgaW50byB0aGlzIHN0YWJsZSBjcm9zcy1lbmdpbmUgdm9jYWJ1bGFyeS4AAAAAAAAAAAAVQ2Fub25pY2FsU3RyZWFtU3RhdHVzAAAAAAAABgAAAAAAAAAHUGVuZGluZwAAAAAAAAAAAAAAAAZBY3RpdmUAAAAAAAEAAAAAAAAABlBhdXNlZAAAAAAAAgAAAAAAAAAIQ2FuY2VsZWQAAAADAAAAAAAAAAlDb21wbGV0ZWQAAAAAAAAEAAAAAAAAAAZGYWlsZWQAAAAAAAU=", "AAAABAAAACtFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFN0cmVhbSBORlQgY29udHJhY3QuAAAAAAAAAAAITmZ0RXJyb3IAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAMkAAAAAAAAADU5vdEF1dGhvcml6ZWQAAAAAAADKAAAAAAAAAA1Ub2tlbk5vdEZvdW5kAAAAAAAAywAAAAAAAAAPTm90VHJhbnNmZXJhYmxlAAAAAMwAAAAhVG9rZW4gSUQgaGFzIGFscmVhZHkgYmVlbiBtaW50ZWQuAAAAAAAADUFscmVhZHlNaW50ZWQAAAAAAADN", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABUAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQ==", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAoAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4=", - "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAEAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEw", - "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAANAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAAA4TnVtYmVyIG9mIE5GVHMgb3duZWQgYnkgYW4gYWRkcmVzcyAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAKTmZ0QmFsYW5jZQAAAAAAAQAAABMAAAABAAAAO1Rva2VuIG1ldGFkYXRhLCBlLmcuLCBuYW1lLCBzeW1ib2wsIFVSSSAoSW5zdGFuY2Ugc3RvcmFnZSkuAAAAAA1Ub2tlbk1ldGFkYXRhAAAAAAAAAQAAABEAAAAAAAAALFJvdXRlciBjb25maWd1cmF0aW9uOiBGbG93IGNvbnRyYWN0IGFkZHJlc3MuAAAADEZsb3dDb250cmFjdAAAAAAAAAAuUm91dGVyIGNvbmZpZ3VyYXRpb246IExvY2t1cCBjb250cmFjdCBhZGRyZXNzLgAAAAAADkxvY2t1cENvbnRyYWN0AAAAAAAAAAAAK1JvdXRlciBjb25maWd1cmF0aW9uOiBORlQgY29udHJhY3QgYWRkcmVzcy4AAAAAC05mdENvbnRyYWN0AAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABYAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQAAAD5UaGUgdG9rZW4gZGlkIG5vdCBkZWJpdCBhbmQgY3JlZGl0IHRoZSBleGFjdCByZXF1ZXN0ZWQgYW1vdW50LgAAAAAAFVRva2VuVHJhbnNmZXJNaXNtYXRjaAAAAAAAABY=", + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAwAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4AAABLU3RhcnQgYW5kIGNsaWZmIHVubG9jayBhbW91bnRzIG11c3QgYmUgbm9ubmVnYXRpdmUgYW5kIGZpdCBpbiB0b3RhbCBhbW91bnQuAAAAABNJbnZhbGlkVW5sb2NrQW1vdW50AAAAAG8AAAA+VGhlIHRva2VuIGRpZCBub3QgZGViaXQgYW5kIGNyZWRpdCB0aGUgZXhhY3QgcmVxdWVzdGVkIGFtb3VudC4AAAAAABVUb2tlblRyYW5zZmVyTWlzbWF0Y2gAAAAAAABw", + "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEwAAAAMENvcmUgY29udHJhY3QgYWRkcmVzc2VzIHdlcmUgYWxyZWFkeSBjb25maWd1cmVkLgAAABFBbHJlYWR5Q29uZmlndXJlZAAAAAAAATE=", + "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAAPAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAABFSW1tdXRhYmxlIHBlci1zdHJlYW0gTkZUIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAAAAAEVRva2VuVHJhbnNmZXJhYmxlAAAAAAAAAQAAAAsAAAABAAAAOE51bWJlciBvZiBORlRzIG93bmVkIGJ5IGFuIGFkZHJlc3MgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAACk5mdEJhbGFuY2UAAAAAAAEAAAATAAAAAQAAADtUb2tlbiBtZXRhZGF0YSwgZS5nLiwgbmFtZSwgc3ltYm9sLCBVUkkgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAANVG9rZW5NZXRhZGF0YQAAAAAAAAEAAAARAAAAAAAAACxSb3V0ZXIgY29uZmlndXJhdGlvbjogRmxvdyBjb250cmFjdCBhZGRyZXNzLgAAAAxGbG93Q29udHJhY3QAAAAAAAAALlJvdXRlciBjb25maWd1cmF0aW9uOiBMb2NrdXAgY29udHJhY3QgYWRkcmVzcy4AAAAAAA5Mb2NrdXBDb250cmFjdAAAAAAAAAAAACtSb3V0ZXIgY29uZmlndXJhdGlvbjogTkZUIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAtOZnRDb250cmFjdAAAAAAAAAAAO0NvcmUtZW5naW5lIGNvbmZpZ3VyYXRpb246IHRydXN0ZWQgUm91dGVyIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAZSb3V0ZXIAAAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), options ) } public readonly fromJSON = { upgrade: this.txFromJSON, + owner_of: this.txFromJSON, withdraw: this.txFromJSON, - initialize: this.txFromJSON, + configure: this.txFromJSON, + set_admin: this.txFromJSON, + status_of: this.txFromJSON, + void_flow: this.txFromJSON, + get_stream: this.txFromJSON, + stream_type: this.txFromJSON, upgrade_nft: this.txFromJSON, withdraw_max: this.txFromJSON, + core_stream_id: this.txFromJSON, create_flow_stream: this.txFromJSON, create_lockup_stream: this.txFromJSON } diff --git a/src/generated/stream_nft/src/index.ts b/src/generated/stream_nft/src/index.ts index 72de5fd..cc66fcb 100644 --- a/src/generated/stream_nft/src/index.ts +++ b/src/generated/stream_nft/src/index.ts @@ -27,7 +27,7 @@ export * as rpc from "@stellar/stellar-sdk/rpc"; /** * Core data structure for a Flow (open-ended, rate-per-second) stream. - * + * * Ported from Sablier's `Flow.Stream` struct with these adaptations: * * - `UD21x18 ratePerSecond` → `i128 rate_per_second` scaled to 18 decimals. @@ -219,6 +219,37 @@ export enum StreamStatus { } +/** + * Stable public metadata for a Stream NFT and its underlying core stream. + */ +export interface StreamMetadata { + /** + * Internal identifier assigned by the selected core engine. + */ +core_stream_id: u64; + /** + * Current holder of withdrawal and NFT-owner Flow void rights. + */ +owner: string; + /** + * Canonical cross-engine lifecycle state. + */ +status: CanonicalStreamStatus; + /** + * Core engine kind. + */ +stream_type: StreamType; + /** + * Public, deployment-scoped stream identifier. + */ +token_id: i128; + /** + * Immutable transferability policy selected when the stream is created. + */ +transferable: boolean; +} + + /** * Parameters for creating a new Lockup stream. * @@ -272,6 +303,21 @@ token: string; total_amount: i128; } +/** + * Canonical lifecycle exposed by the Router to every product surface. + * + * Core engines retain their more detailed accounting states; the Router maps + * those states into this stable cross-engine vocabulary. + */ +export enum CanonicalStreamStatus { + Pending = 0, + Active = 1, + Paused = 2, + Canceled = 3, + Completed = 4, + Failed = 5, +} + /** * Errors specific to the Stream NFT contract. */ @@ -373,7 +419,11 @@ export const FlowError = { /** * Rate per second must not be negative. */ - 21: {message:"NegativeRate"} + 21: {message:"NegativeRate"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 22: {message:"TokenTransferMismatch"} } /** @@ -419,7 +469,15 @@ export const LockupError = { /** * Sender and recipient must be different addresses. */ - 110: {message:"SenderEqualsRecipient"} + 110: {message:"SenderEqualsRecipient"}, + /** + * Start and cliff unlock amounts must be nonnegative and fit in total amount. + */ + 111: {message:"InvalidUnlockAmount"}, + /** + * The token did not debit and credit the exact requested amount. + */ + 112: {message:"TokenTransferMismatch"} } /** @@ -429,7 +487,11 @@ export const RouterError = { 301: {message:"AlreadyInitialized"}, 302: {message:"NotInitialized"}, 303: {message:"NotAuthorized"}, - 304: {message:"InvalidStreamType"} + 304: {message:"InvalidStreamType"}, + /** + * Core contract addresses were already configured. + */ + 305: {message:"AlreadyConfigured"} } /** @@ -438,22 +500,15 @@ export const RouterError = { * Using a typed enum prevents key collisions (SKILL.md §3). * Keys are namespaced by variant to keep different data types separate. */ -export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "AllowedFeeTokens", values: void}; +export type DataKey = {tag: "Admin", values: void} | {tag: "NextStreamId", values: void} | {tag: "FlowStream", values: readonly [u64]} | {tag: "LockupStream", values: readonly [u64]} | {tag: "AggregateBalance", values: readonly [string]} | {tag: "TokenOwner", values: readonly [i128]} | {tag: "TokenStreamData", values: readonly [i128]} | {tag: "TokenTransferable", values: readonly [i128]} | {tag: "NftBalance", values: readonly [string]} | {tag: "TokenMetadata", values: readonly [string]} | {tag: "FlowContract", values: void} | {tag: "LockupContract", values: void} | {tag: "NftContract", values: void} | {tag: "Router", values: void} | {tag: "AllowedFeeTokens", values: void}; export interface Client { - /** - * Construct and simulate a burn transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Burn an NFT (e.g. when stream is depleted or voided). - * Only admin (Router) can burn. - */ - burn: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> - /** * Construct and simulate a mint transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Mint a new NFT representing a stream. * Only the admin (Router) can mint. */ - mint: ({to, stream_type, stream_id, token_id}: {to: string, stream_type: StreamType, stream_id: u64, token_id: i128}, options?: MethodOptions) => Promise> + mint: ({to, stream_type, stream_id, token_id, transferable}: {to: string, stream_type: StreamType, stream_id: u64, token_id: i128, transferable: boolean}, options?: MethodOptions) => Promise> /** * Construct and simulate a name transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. @@ -491,21 +546,23 @@ export interface Client { */ transfer: ({from, to, token_id}: {from: string, to: string, token_id: i128}, options?: MethodOptions) => Promise> - /** - * Construct and simulate a initialize transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. - * Initialize the NFT contract. - */ - initialize: ({admin, name, symbol}: {admin: string, name: string, symbol: string}, options?: MethodOptions) => Promise> - /** * Construct and simulate a get_stream_data transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. * Get the stream details associated with an NFT. */ get_stream_data: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + /** + * Construct and simulate a is_transferable transaction. Returns an `AssembledTransaction` object which will have a `result` field containing the result of the simulation. If this transaction changes contract state, you will need to call `signAndSend()` on the returned object. + * Return the immutable per-stream transferability policy. + */ + is_transferable: ({token_id}: {token_id: i128}, options?: MethodOptions) => Promise> + } export class Client extends ContractClient { static async deploy( + /** Constructor/Initialization Args for the contract's `__constructor` method */ + {admin, name, symbol}: {admin: string, name: string, symbol: string}, /** Options for initializing a Client as well as for calling a method, with extras specific to deploying. */ options: MethodOptions & Omit & { @@ -517,44 +574,45 @@ export class Client extends ContractClient { format?: "hex" | "base64"; } ): Promise> { - return ContractClient.deploy(null, options) + return ContractClient.deploy({admin, name, symbol}, options) } constructor(public readonly options: ContractClientOptions) { super( - new ContractSpec([ "AAAAAAAAAFNCdXJuIGFuIE5GVCAoZS5nLiB3aGVuIHN0cmVhbSBpcyBkZXBsZXRlZCBvciB2b2lkZWQpLgpPbmx5IGFkbWluIChSb3V0ZXIpIGNhbiBidXJuLgAAAAAEYnVybgAAAAEAAAAAAAAACHRva2VuX2lkAAAACwAAAAA=", - "AAAAAAAAAEdNaW50IGEgbmV3IE5GVCByZXByZXNlbnRpbmcgYSBzdHJlYW0uCk9ubHkgdGhlIGFkbWluIChSb3V0ZXIpIGNhbiBtaW50LgAAAAAEbWludAAAAAQAAAAAAAAAAnRvAAAAAAATAAAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAAAAAAIdG9rZW5faWQAAAALAAAAAA==", + new ContractSpec([ "AAAAAAAAAEdNaW50IGEgbmV3IE5GVCByZXByZXNlbnRpbmcgYSBzdHJlYW0uCk9ubHkgdGhlIGFkbWluIChSb3V0ZXIpIGNhbiBtaW50LgAAAAAEbWludAAAAAUAAAAAAAAAAnRvAAAAAAATAAAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAAAAAAAAJc3RyZWFtX2lkAAAAAAAABgAAAAAAAAAIdG9rZW5faWQAAAALAAAAAAAAAAx0cmFuc2ZlcmFibGUAAAABAAAAAA==", "AAAAAAAAAA5HZXQgdG9rZW4gbmFtZQAAAAAABG5hbWUAAAAAAAAAAQAAABA=", "AAAAAAAAABBHZXQgdG9rZW4gc3ltYm9sAAAABnN5bWJvbAAAAAAAAAAAAAEAAAAQ", "AAAAAAAAACtHZXQgdGhlIG51bWJlciBvZiBORlRzIG93bmVkIGJ5IGFuIGFkZHJlc3MuAAAAAAdiYWxhbmNlAAAAAAEAAAAAAAAABW93bmVyAAAAAAAAEwAAAAEAAAAL", "AAAAAAAAACVBZG1pbiBjYW4gdXBncmFkZSB0aGUgY29udHJhY3QgbG9naWMuAAAAAAAAB3VwZ3JhZGUAAAAAAQAAAAAAAAANbmV3X3dhc21faGFzaAAAAAAAA+4AAAAgAAAAAA==", "AAAAAAAAABhHZXQgdGhlIG93bmVyIG9mIGFuIE5GVC4AAAAIb3duZXJfb2YAAAABAAAAAAAAAAh0b2tlbl9pZAAAAAsAAAABAAAAEw==", "AAAAAAAAAB9UcmFuc2ZlciBhbiBORlQgdG8gYSBuZXcgb3duZXIuAAAAAAh0cmFuc2ZlcgAAAAMAAAAAAAAABGZyb20AAAATAAAAAAAAAAJ0bwAAAAAAEwAAAAAAAAAIdG9rZW5faWQAAAALAAAAAA==", - "AAAAAAAAABxJbml0aWFsaXplIHRoZSBORlQgY29udHJhY3QuAAAACmluaXRpYWxpemUAAAAAAAMAAAAAAAAABWFkbWluAAAAAAAAEwAAAAAAAAAEbmFtZQAAABAAAAAAAAAABnN5bWJvbAAAAAAAEAAAAAA=", + "AAAAAAAAADlBdG9taWNhbGx5IGluaXRpYWxpemUgdGhlIE5GVCBjb250cmFjdCBkdXJpbmcgZGVwbG95bWVudC4AAAAAAAANX19jb25zdHJ1Y3RvcgAAAAAAAAMAAAAAAAAABWFkbWluAAAAAAAAEwAAAAAAAAAEbmFtZQAAABAAAAAAAAAABnN5bWJvbAAAAAAAEAAAAAA=", "AAAAAAAAAC5HZXQgdGhlIHN0cmVhbSBkZXRhaWxzIGFzc29jaWF0ZWQgd2l0aCBhbiBORlQuAAAAAAAPZ2V0X3N0cmVhbV9kYXRhAAAAAAEAAAAAAAAACHRva2VuX2lkAAAACwAAAAEAAAPtAAAAAgAAB9AAAAAKU3RyZWFtVHlwZQAAAAAABg==", + "AAAAAAAAADdSZXR1cm4gdGhlIGltbXV0YWJsZSBwZXItc3RyZWFtIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kuAAAAAA9pc190cmFuc2ZlcmFibGUAAAAAAQAAAAAAAAAIdG9rZW5faWQAAAALAAAAAQAAAAE=", "AAAAAQAAA3pDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIEZsb3cgKG9wZW4tZW5kZWQsIHJhdGUtcGVyLXNlY29uZCkgc3RyZWFtLgoKUG9ydGVkIGZyb20gU2FibGllcidzIGBGbG93LlN0cmVhbWAgc3RydWN0IHdpdGggdGhlc2UgYWRhcHRhdGlvbnM6CgotIGBVRDIxeDE4IHJhdGVQZXJTZWNvbmRgIOKGkiBgaTEyOCByYXRlX3Blcl9zZWNvbmRgIHNjYWxlZCB0byAxOCBkZWNpbWFscy4KU29yb2JhbiBoYXMgbm8gbmF0aXZlIGZpeGVkLXBvaW50IHR5cGUsIHNvIHdlIHVzZSBpMTI4IHdpdGggbWFudWFsIHNjYWxpbmcuCkEgcmF0ZSBvZiAxIHRva2VuL3NlYyBmb3IgYSA3LWRlY2ltYWwgdG9rZW4gPSAxZTE4IGludGVybmFsbHkuCgotIGBJRVJDMjAgdG9rZW5gIOKGkiBgQWRkcmVzc2AgKFNvcm9iYW4gdG9rZW4gY29udHJhY3QgYWRkcmVzcykuCgotIGB1aW50MTI4IGJhbGFuY2VgIOKGkiBgaTEyOGAgKFNvcm9iYW4gU0RLIGNvbnZlbnRpb24gZm9yIHRva2VuIGFtb3VudHMpLgoKLSBgdWludDQwIHNuYXBzaG90VGltZWAg4oaSIGB1NjRgIChTb3JvYmFuIGxlZGdlciB0aW1lc3RhbXAgaXMgdTY0KS4KCi0gYGJvb2wgaXNTdHJlYW1gIHNlbnRpbmVsIHJlbW92ZWQg4oCUIHdlIHVzZSBzdG9yYWdlIGtleSBleGlzdGVuY2UgaW5zdGVhZC4KCi0gYGJvb2wgaXNUcmFuc2ZlcmFibGVgIHJlbW92ZWQg4oCUIGRlbGVnYXRlZCB0byB0aGUgTkZUIGNvbnRyYWN0IGxheWVyLgoKIyBTdG9yYWdlIExheW91dAoKRWFjaCBzdHJlYW0gaXMgc3RvcmVkIHVuZGVyIGBEYXRhS2V5OjpTdHJlYW0oc3RyZWFtX2lkKWAgaW4gcGVyc2lzdGVudCBzdG9yYWdlLgpUaGUgc3RydWN0IGlzIGtlcHQgYXMgZmxhdCBhcyBwb3NzaWJsZSB0byBtaW5pbWl6ZSBzZXJpYWxpemF0aW9uIG92ZXJoZWFkLgAAAAAAAAAAAApGbG93U3RyZWFtAAAAAAAJAAAATkN1cnJlbnQgYmFsYW5jZSBoZWxkIGluIHRoZSBzdHJlYW0gKGRlcG9zaXRlZCAtIHdpdGhkcmF3biksIGluIHRva2VuIGRlY2ltYWxzLgAAAAAAB2JhbGFuY2UAAAAACwAAAC9XaGV0aGVyIHRoZSBzdHJlYW0gaGFzIGJlZW4gcGVybWFuZW50bHkgdm9pZGVkLgAAAAAJaXNfdm9pZGVkAAAAAAAAAQAAAKVSYXRlIGF0IHdoaWNoIGRlYnQgYWNjcnVlcywgaW4gMTgtZGVjaW1hbCBmaXhlZC1wb2ludC4KMCBtZWFucyB0aGUgc3RyZWFtIGlzIHBhdXNlZC4KRXhhbXBsZTogMSB0b2tlbi9zZWMgZm9yIGEgNy1kZWNpbWFsIHRva2VuIOKGkiAxXzAwMF8wMDBfMDAwXzAwMF8wMDBfMDAwICgxZTE4KS4AAAAAAAAPcmF0ZV9wZXJfc2Vjb25kAAAAAAsAAAAwVGhlIGFkZHJlc3MgcmVjZWl2aW5nIHRoZSB0b2tlbnMgKGNhbiB3aXRoZHJhdykuAAAACXJlY2lwaWVudAAAAAAAABMAAAA9VGhlIGFkZHJlc3Mgc3RyZWFtaW5nIHRoZSB0b2tlbnMgKGNhbiBwYXVzZSwgYWRqdXN0LCByZWZ1bmQpLgAAAAAAAAZzZW5kZXIAAAAAABMAAAB6QWNjdW11bGF0ZWQgZGVidCBhdCB0aGUgbGFzdCBzbmFwc2hvdCwgaW4gMTgtZGVjaW1hbCBmaXhlZC1wb2ludC4KVG90YWwgZGVidCA9IHNuYXBzaG90X2RlYnRfc2NhbGVkICsgb25nb2luZ19kZWJ0X3NjYWxlZC4AAAAAABRzbmFwc2hvdF9kZWJ0X3NjYWxlZAAAAAsAAABkVW5peCB0aW1lc3RhbXAgb2YgdGhlIGxhc3QgZGVidCBzbmFwc2hvdC4KRGVidCBhY2NydWVzIGZyb20gdGhpcyBwb2ludCBmb3J3YXJkIGF0IGByYXRlX3Blcl9zZWNvbmRgLgAAAA1zbmFwc2hvdF90aW1lAAAAAAAABgAAADpUaGUgU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzIChTQUMgb3IgY3VzdG9tIFNFUC00MSkuAAAAAAAFdG9rZW4AAAAAAAATAAAAbU51bWJlciBvZiBkZWNpbWFscyBmb3IgdGhlIHRva2VuIChlLmcuIDcgZm9yIG1vc3QgU3RlbGxhciBhc3NldHMpLgpVc2VkIGZvciBzY2FsZS9kZXNjYWxlIG9wZXJhdGlvbnMuIE1heCAxOC4AAAAAAAAOdG9rZW5fZGVjaW1hbHMAAAAAAAQ=", "AAAAAwAAAGhUeXBlIG9mIHN0cmVhbSAoRmxvdyBvciBMb2NrdXApLgpVc2VkIGJ5IHRoZSBTdHJlYW0gTkZUIGFuZCBSb3V0ZXIgdG8gcm91dGUgbG9naWMgdG8gdGhlIGNvcnJlY3QgZW5naW5lLgAAAAAAAAAKU3RyZWFtVHlwZQAAAAAAAgAAAAAAAAAERmxvdwAAAAAAAAAAAAAABkxvY2t1cAAAAAAAAQ==", "AAAAAwAAAMtTdGF0dXMgb2YgYSBMb2NrdXAgc3RyZWFtLgoKVXNlcyAidGVtcGVyYXR1cmUiIHNlbWFudGljczoKLSAqKldhcm0qKiAoUGVuZGluZywgU3RyZWFtaW5nKTogdGltZSBhbG9uZSBjYW4gY2hhbmdlIHRoZSBzdGF0dXMuCi0gKipDb2xkKiogKFNldHRsZWQsIENhbmNlbGVkLCBEZXBsZXRlZCk6IHRpbWUgYWxvbmUgY2Fubm90IGNoYW5nZSB0aGUgc3RhdHVzLgAAAAAAAAAADExvY2t1cFN0YXR1cwAAAAUAAAA/Q3JlYXRlZCBidXQgc3RhcnRfdGltZSBpcyBpbiB0aGUgZnV0dXJlLiBObyB0b2tlbnMgaGF2ZSB2ZXN0ZWQuAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlIOKAlCB0b2tlbnMgYXJlIGN1cnJlbnRseSB2ZXN0aW5nIG92ZXIgdGltZS4AAAAAAAlTdHJlYW1pbmcAAAAAAAABAAAAS0FsbCB0b2tlbnMgaGF2ZSBmdWxseSB2ZXN0ZWQuIFJlY2lwaWVudCBjYW4gd2l0aGRyYXcgdGhlIHJlbWFpbmluZyBiYWxhbmNlLgAAAAAHU2V0dGxlZAAAAAACAAAAP1NlbmRlciBjYW5jZWxlZCB0aGUgc3RyZWFtLiBVbnZlc3RlZCB0b2tlbnMgcmV0dXJuZWQgdG8gc2VuZGVyLgAAAAAIQ2FuY2VsZWQAAAADAAAAQkZ1bGx5IHdpdGhkcmF3biAoYW5kL29yIHJlZnVuZGVkKS4gTm8gdG9rZW5zIHJlbWFpbiBpbiB0aGUgc3RyZWFtLgAAAAAACERlcGxldGVkAAAABA==", "AAAAAQAAAtxDb3JlIGRhdGEgc3RydWN0dXJlIGZvciBhIExvY2t1cCAoZml4ZWQtdGVybSB2ZXN0aW5nKSBzdHJlYW0uCgpTdXBwb3J0cyBsaW5lYXIgdW5sb2NrIHdpdGggb3B0aW9uYWwgY2xpZmYuIFRoZSB2ZXN0ZWQgKCJzdHJlYW1lZCIpIGFtb3VudAphdCBhbnkgdGltZSBgdGAgaXMgY2FsY3VsYXRlZCBhczoKCmBgYHRleHQKaWYgdCA8IHN0YXJ0X3RpbWU6ICAgICAgIHZlc3RlZCA9IDAKaWYgdCA8IGNsaWZmX3RpbWU6ICAgICAgIHZlc3RlZCA9IHN0YXJ0X3VubG9ja19hbW91bnQKaWYgdCA+PSBlbmRfdGltZTogICAgICAgICB2ZXN0ZWQgPSB0b3RhbF9hbW91bnQKZWxzZToKZWxhcHNlZCA9IGZsb29yKCh0IC0gY2xpZmZfdGltZSkgLyBncmFudWxhcml0eSkgKiBncmFudWxhcml0eQpzdHJlYW1hYmxlX2R1cmF0aW9uID0gZW5kX3RpbWUgLSBjbGlmZl90aW1lCnN0cmVhbWFibGVfYW1vdW50ID0gdG90YWxfYW1vdW50IC0gc3RhcnRfdW5sb2NrX2Ftb3VudCAtIGNsaWZmX3VubG9ja19hbW91bnQKdmVzdGVkID0gc3RhcnRfdW5sb2NrX2Ftb3VudCArIGNsaWZmX3VubG9ja19hbW91bnQgKyAoZWxhcHNlZCAqIHN0cmVhbWFibGVfYW1vdW50IC8gc3RyZWFtYWJsZV9kdXJhdGlvbikKYGBgCgpUaGlzIG1pcnJvcnMgdGhlIHJlZmVyZW5jZSBsaW5lYXIgbG9ja3VwIGNhbGN1bGF0aW9uIHdpdGggZGlzY3JldGUgdW5sb2NrCnN0ZXBzIGF0IGBncmFudWxhcml0eWAtc2Vjb25kIGludGVydmFscy4AAAAAAAAADExvY2t1cFN0cmVhbQAAAA8AAABGV2hldGhlciB0aGUgc2VuZGVyIGNhbiBjYW5jZWwgdGhpcyBzdHJlYW0gYW5kIHJlY2xhaW0gdW52ZXN0ZWQgdG9rZW5zLgAAAAAACmNhbmNlbGFibGUAAAAAAAEAAABuT3B0aW9uYWwgY2xpZmYgdGltZXN0YW1wLiBObyB0b2tlbnMgYmV5b25kIGBzdGFydF91bmxvY2tfYW1vdW50YAp2ZXN0IGJlZm9yZSB0aGlzIHRpbWUuIFNldCB0byAwIGZvciBubyBjbGlmZi4AAAAAAApjbGlmZl90aW1lAAAAAAAGAAAAR0Ftb3VudCB1bmxvY2tlZCBhdCBgY2xpZmZfdGltZWAgKGluIGFkZGl0aW9uIHRvIGBzdGFydF91bmxvY2tfYW1vdW50YCkuAAAAABNjbGlmZl91bmxvY2tfYW1vdW50AAAAAAsAAAAxVW5peCB0aW1lc3RhbXAgd2hlbiB0aGUgbG9ja3VwIGlzIGZ1bGx5IHVubG9ja2VkLgAAAAAAAAhlbmRfdGltZQAAAAYAAAB9VW5sb2NrIGdyYW51bGFyaXR5IGluIHNlY29uZHMuIFRva2VucyB2ZXN0IGluIGRpc2NyZXRlIHN0ZXBzIG9mIHRoaXMKaW50ZXJ2YWwuIERlZmF1bHQgPSAxIChwZXItc2Vjb25kIHZlc3RpbmcpLiBNdXN0IGJlID4gMC4AAAAAAAALZ3JhbnVsYXJpdHkAAAAABgAAADdXaGV0aGVyIGFsbCB0b2tlbnMgaGF2ZSBiZWVuIHdpdGhkcmF3biBhbmQvb3IgcmVmdW5kZWQuAAAAAAtpc19kZXBsZXRlZAAAAAABAAAAP1RoZSBhZGRyZXNzIHRoYXQgcmVjZWl2ZXMgdG9rZW5zIGFzIHRoZXkgdW5sb2NrIChjYW4gd2l0aGRyYXcpLgAAAAAJcmVjaXBpZW50AAAAAAAAEwAAAEFBbW91bnQgcmVmdW5kZWQgdG8gc2VuZGVyIG9uIGNhbmNlbGxhdGlvbi4gWmVybyB1bmxlc3MgY2FuY2VsbGVkLgAAAAAAAA9yZWZ1bmRlZF9hbW91bnQAAAAACwAAAExUaGUgYWRkcmVzcyB0aGF0IGNyZWF0ZWQgYW5kIGZ1bmRlZCB0aGUgbG9ja3VwIChjYW4gY2FuY2VsIGlmIGBjYW5jZWxhYmxlYCkuAAAABnNlbmRlcgAAAAAAEwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHRoZSBsb2NrdXAgYmVnaW5zLgAAAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAsQW1vdW50IHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IGBzdGFydF90aW1lYC4AAAATc3RhcnRfdW5sb2NrX2Ftb3VudAAAAAALAAAAI1RoZSBTb3JvYmFuIHRva2VuIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAV0b2tlbgAAAAAAABMAAAA7VG90YWwgYW1vdW50IGRlcG9zaXRlZCBpbnRvIHRoZSBzdHJlYW0gKGluIHRva2VuIGRlY2ltYWxzKS4AAAAADHRvdGFsX2Ftb3VudAAAAAsAAAAmV2hldGhlciB0aGUgc3RyZWFtIGhhcyBiZWVuIGNhbmNlbGxlZC4AAAAAAAx3YXNfY2FuY2VsZWQAAAABAAAALUN1bXVsYXRpdmUgYW1vdW50IHdpdGhkcmF3biBieSB0aGUgcmVjaXBpZW50LgAAAAAAABB3aXRoZHJhd25fYW1vdW50AAAACw==", "AAAAAwAAALFTdGF0dXMgb2YgYSBGbG93IHN0cmVhbS4KClRoZSBzdGF0dXMgaXMgZGVyaXZlZCBmcm9tIHRoZSBzdHJlYW0ncyBjdXJyZW50IHN0YXRlIChyYXRlLCBiYWxhbmNlLCBkZWJ0LCB2b2lkZWQgZmxhZykgcmF0aGVyIHRoYW4KYmVpbmcgc3RvcmVkIGRpcmVjdGx5IOKAlCBrZWVwaW5nIHN0b3JhZ2UgbWluaW1hbC4AAAAAAAAAAAAADFN0cmVhbVN0YXR1cwAAAAYAAAA+U3RyZWFtIHNjaGVkdWxlZCB0byBzdGFydCBpbiB0aGUgZnV0dXJlIChzbmFwc2hvdF90aW1lID4gbm93KS4AAAAAAAdQZW5kaW5nAAAAAAAAAAAyQWN0aXZlbHkgc3RyZWFtaW5nIHdpdGggYmFsYW5jZSBjb3ZlcmluZyBhbGwgZGVidC4AAAAAABBTdHJlYW1pbmdTb2x2ZW50AAAAAQAAADZBY3RpdmVseSBzdHJlYW1pbmcgYnV0IGRlYnQgZXhjZWVkcyBhdmFpbGFibGUgYmFsYW5jZS4AAAAAABJTdHJlYW1pbmdJbnNvbHZlbnQAAAAAAAIAAAAoUGF1c2VkIGJ5IHNlbmRlciB3aXRoIG5vIHVuY292ZXJlZCBkZWJ0LgAAAA1QYXVzZWRTb2x2ZW50AAAAAAAAAwAAACVQYXVzZWQgYnkgc2VuZGVyIHdpdGggdW5jb3ZlcmVkIGRlYnQuAAAAAAAAD1BhdXNlZEluc29sdmVudAAAAAAEAAAARVBlcm1hbmVudGx5IHN0b3BwZWQuIENhbm5vdCBiZSByZXN0YXJ0ZWQuIFVuY292ZXJlZCBkZWJ0IHdyaXR0ZW4gb2ZmLgAAAAAAAAZWb2lkZWQAAAAAAAU=", + "AAAAAQAAAEdTdGFibGUgcHVibGljIG1ldGFkYXRhIGZvciBhIFN0cmVhbSBORlQgYW5kIGl0cyB1bmRlcmx5aW5nIGNvcmUgc3RyZWFtLgAAAAAAAAAADlN0cmVhbU1ldGFkYXRhAAAAAAAGAAAAOUludGVybmFsIGlkZW50aWZpZXIgYXNzaWduZWQgYnkgdGhlIHNlbGVjdGVkIGNvcmUgZW5naW5lLgAAAAAAAA5jb3JlX3N0cmVhbV9pZAAAAAAABgAAADxDdXJyZW50IGhvbGRlciBvZiB3aXRoZHJhd2FsIGFuZCBORlQtb3duZXIgRmxvdyB2b2lkIHJpZ2h0cy4AAAAFb3duZXIAAAAAAAATAAAAJ0Nhbm9uaWNhbCBjcm9zcy1lbmdpbmUgbGlmZWN5Y2xlIHN0YXRlLgAAAAAGc3RhdHVzAAAAAAfQAAAAFUNhbm9uaWNhbFN0cmVhbVN0YXR1cwAAAAAAABFDb3JlIGVuZ2luZSBraW5kLgAAAAAAAAtzdHJlYW1fdHlwZQAAAAfQAAAAClN0cmVhbVR5cGUAAAAAACxQdWJsaWMsIGRlcGxveW1lbnQtc2NvcGVkIHN0cmVhbSBpZGVudGlmaWVyLgAAAAh0b2tlbl9pZAAAAAsAAABFSW1tdXRhYmxlIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgc2VsZWN0ZWQgd2hlbiB0aGUgc3RyZWFtIGlzIGNyZWF0ZWQuAAAAAAAADHRyYW5zZmVyYWJsZQAAAAE=", "AAAAAQAAAINQYXJhbWV0ZXJzIGZvciBjcmVhdGluZyBhIG5ldyBMb2NrdXAgc3RyZWFtLgoKQnVuZGxlZCBpbnRvIGEgc3RydWN0IGJlY2F1c2UgU29yb2JhbiBjb250cmFjdCBmdW5jdGlvbnMKaGF2ZSBhIG1heCBvZiAxMCBwYXJhbWV0ZXJzLgAAAAAAAAAAEkNyZWF0ZUxvY2t1cFBhcmFtcwAAAAAACwAAAClXaGV0aGVyIHRoZSBzZW5kZXIgY2FuIGNhbmNlbCB0aGUgc3RyZWFtLgAAAAAAAApjYW5jZWxhYmxlAAAAAAABAAAAME9wdGlvbmFsIGNsaWZmIHRpbWVzdGFtcC4gU2V0IHRvIDAgZm9yIG5vIGNsaWZmLgAAAApjbGlmZl90aW1lAAAAAAAGAAAAKlRva2VucyB1bmxvY2tlZCBhdCBjbGlmZiAoYWRkZWQgdG8gc3RhcnQpLgAAAAAAE2NsaWZmX3VubG9ja19hbW91bnQAAAAACwAAACZVbml4IHRpbWVzdGFtcCB3aGVuIHZlc3RpbmcgY29tcGxldGVzLgAAAAAACGVuZF90aW1lAAAABgAAADFVbmxvY2sgc3RlcCBpbnRlcnZhbCBpbiBzZWNvbmRzLiAwIGRlZmF1bHRzIHRvIDEuAAAAAAAAC2dyYW51bGFyaXR5AAAAAAYAAAAgQWRkcmVzcyByZWNlaXZpbmcgdmVzdGVkIHRva2Vucy4AAAAJcmVjaXBpZW50AAAAAAAAEwAAADhBZGRyZXNzIGZ1bmRpbmcgdGhlIHN0cmVhbSAoY2FuIGNhbmNlbCBpZiBgY2FuY2VsYWJsZWApLgAAAAZzZW5kZXIAAAAAABMAAAAjVW5peCB0aW1lc3RhbXAgd2hlbiB2ZXN0aW5nIGJlZ2lucy4AAAAACnN0YXJ0X3RpbWUAAAAAAAYAAAAlVG9rZW5zIHVubG9ja2VkIGltbWVkaWF0ZWx5IGF0IHN0YXJ0LgAAAAAAABNzdGFydF91bmxvY2tfYW1vdW50AAAAAAsAAAAfU29yb2JhbiB0b2tlbiBjb250cmFjdCBhZGRyZXNzLgAAAAAFdG9rZW4AAAAAAAATAAAAKVRvdGFsIHRva2VucyB0byB2ZXN0IChpbiB0b2tlbiBkZWNpbWFscykuAAAAAAAADHRvdGFsX2Ftb3VudAAAAAs=", + "AAAAAwAAAMZDYW5vbmljYWwgbGlmZWN5Y2xlIGV4cG9zZWQgYnkgdGhlIFJvdXRlciB0byBldmVyeSBwcm9kdWN0IHN1cmZhY2UuCgpDb3JlIGVuZ2luZXMgcmV0YWluIHRoZWlyIG1vcmUgZGV0YWlsZWQgYWNjb3VudGluZyBzdGF0ZXM7IHRoZSBSb3V0ZXIgbWFwcwp0aG9zZSBzdGF0ZXMgaW50byB0aGlzIHN0YWJsZSBjcm9zcy1lbmdpbmUgdm9jYWJ1bGFyeS4AAAAAAAAAAAAVQ2Fub25pY2FsU3RyZWFtU3RhdHVzAAAAAAAABgAAAAAAAAAHUGVuZGluZwAAAAAAAAAAAAAAAAZBY3RpdmUAAAAAAAEAAAAAAAAABlBhdXNlZAAAAAAAAgAAAAAAAAAIQ2FuY2VsZWQAAAADAAAAAAAAAAlDb21wbGV0ZWQAAAAAAAAEAAAAAAAAAAZGYWlsZWQAAAAAAAU=", "AAAABAAAACtFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFN0cmVhbSBORlQgY29udHJhY3QuAAAAAAAAAAAITmZ0RXJyb3IAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAMkAAAAAAAAADU5vdEF1dGhvcml6ZWQAAAAAAADKAAAAAAAAAA1Ub2tlbk5vdEZvdW5kAAAAAAAAywAAAAAAAAAPTm90VHJhbnNmZXJhYmxlAAAAAMwAAAAhVG9rZW4gSUQgaGFzIGFscmVhZHkgYmVlbiBtaW50ZWQuAAAAAAAADUFscmVhZHlNaW50ZWQAAAAAAADN", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABUAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQ==", - "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAoAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4=", - "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAEAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEw", - "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAANAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAAA4TnVtYmVyIG9mIE5GVHMgb3duZWQgYnkgYW4gYWRkcmVzcyAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAKTmZ0QmFsYW5jZQAAAAAAAQAAABMAAAABAAAAO1Rva2VuIG1ldGFkYXRhLCBlLmcuLCBuYW1lLCBzeW1ib2wsIFVSSSAoSW5zdGFuY2Ugc3RvcmFnZSkuAAAAAA1Ub2tlbk1ldGFkYXRhAAAAAAAAAQAAABEAAAAAAAAALFJvdXRlciBjb25maWd1cmF0aW9uOiBGbG93IGNvbnRyYWN0IGFkZHJlc3MuAAAADEZsb3dDb250cmFjdAAAAAAAAAAuUm91dGVyIGNvbmZpZ3VyYXRpb246IExvY2t1cCBjb250cmFjdCBhZGRyZXNzLgAAAAAADkxvY2t1cENvbnRyYWN0AAAAAAAAAAAAK1JvdXRlciBjb25maWd1cmF0aW9uOiBORlQgY29udHJhY3QgYWRkcmVzcy4AAAAAC05mdENvbnRyYWN0AAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgRmxvdyBzdHJlYW1pbmcgY29udHJhY3QuAAAAAAAAAAAACUZsb3dFcnJvcgAAAAAAABYAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAAQAAACxDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQgZm9yIHRoaXMgb3BlcmF0aW9uLgAAAAxVbmF1dGhvcml6ZWQAAAACAAAANlN0cmVhbSBpcyBwYXVzZWQ7IG9wZXJhdGlvbiByZXF1aXJlcyBhY3RpdmUgc3RyZWFtaW5nLgAAAAAADFN0cmVhbVBhdXNlZAAAAAMAAAAvU3RyZWFtIGlzIHZvaWRlZDsgbm8gZnVydGhlciBtdXRhdGlvbnMgYWxsb3dlZC4AAAAADFN0cmVhbVZvaWRlZAAAAAQAAAA3U3RyZWFtIGlzIG5vdCBwYXVzZWQ7IHJlc3RhcnQgcmVxdWlyZXMgYSBwYXVzZWQgc3RyZWFtLgAAAAAPU3RyZWFtTm90UGF1c2VkAAAAAAUAAAA5U3RyZWFtIGhhcyBub3Qgc3RhcnRlZCB5ZXQgKHNuYXBzaG90X3RpbWUgaW4gdGhlIGZ1dHVyZSkuAAAAAAAADVN0cmVhbVBlbmRpbmcAAAAAAAAGAAAAIE5ldyByYXRlIHBlciBzZWNvbmQgbXVzdCBiZSA+IDAuAAAAEVJhdGVQZXJTZWNvbmRaZXJvAAAAAAAABwAAACdOZXcgcmF0ZSBtdXN0IGRpZmZlciBmcm9tIGN1cnJlbnQgcmF0ZS4AAAAAEFJhdGVOb3REaWZmZXJlbnQAAAAIAAAAG0RlcG9zaXQgYW1vdW50IG11c3QgYmUgPiAwLgAAAAARRGVwb3NpdEFtb3VudFplcm8AAAAAAAAJAAAAHFdpdGhkcmF3IGFtb3VudCBtdXN0IGJlID4gMC4AAAASV2l0aGRyYXdBbW91bnRaZXJvAAAAAAAKAAAALVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHdpdGhkcmF3YWJsZSBiYWxhbmNlLgAAAAAAAAhPdmVyZHJhdwAAAAsAAAAaUmVmdW5kIGFtb3VudCBtdXN0IGJlID4gMC4AAAAAABBSZWZ1bmRBbW91bnRaZXJvAAAADAAAAClSZWZ1bmQgYW1vdW50IGV4Y2VlZHMgcmVmdW5kYWJsZSBiYWxhbmNlLgAAAAAAAA5SZWZ1bmRPdmVyZmxvdwAAAAAADQAAACVUb2tlbiBoYXMgPiAxOCBkZWNpbWFscywgdW5zdXBwb3J0ZWQuAAAAAAAAFEludmFsaWRUb2tlbkRlY2ltYWxzAAAADgAAADZTdHJlYW0gYmFsYW5jZSBpcyB6ZXJvIChlLmcuIHF1ZXJ5aW5nIGRlcGxldGlvbiB0aW1lKS4AAAAAAAtCYWxhbmNlWmVybwAAAAAPAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAEAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAEQAAADhDYW5ub3QgY3JlYXRlIGEgcGVuZGluZyBzdHJlYW0gd2l0aCByYXRlX3Blcl9zZWNvbmQgPSAwLgAAABdDcmVhdGVSYXRlUGVyU2Vjb25kWmVybwAAAAASAAAAOUludGVybmFsIG1hdGggZXJyb3Ig4oCUIHNob3VsZCBuZXZlciBvY2N1ciBpbiBwcm9kdWN0aW9uLgAAAAAAABJJbnZhbGlkQ2FsY3VsYXRpb24AAAAAABMAAAAxU2VuZGVyIGFuZCByZWNpcGllbnQgbXVzdCBiZSBkaWZmZXJlbnQgYWRkcmVzc2VzLgAAAAAAABVTZW5kZXJFcXVhbHNSZWNpcGllbnQAAAAAAAAUAAAAJVJhdGUgcGVyIHNlY29uZCBtdXN0IG5vdCBiZSBuZWdhdGl2ZS4AAAAAAAAMTmVnYXRpdmVSYXRlAAAAFQAAAD5UaGUgdG9rZW4gZGlkIG5vdCBkZWJpdCBhbmQgY3JlZGl0IHRoZSBleGFjdCByZXF1ZXN0ZWQgYW1vdW50LgAAAAAAFVRva2VuVHJhbnNmZXJNaXNtYXRjaAAAAAAAABY=", + "AAAABAAAAC5FcnJvcnMgZW1pdHRlZCBieSB0aGUgTG9ja3VwIHZlc3RpbmcgY29udHJhY3QuAAAAAAAAAAAAC0xvY2t1cEVycm9yAAAAAAwAAAAZU3RyZWFtIElEIGRvZXMgbm90IGV4aXN0LgAAAAAAAA5TdHJlYW1Ob3RGb3VuZAAAAAAAZQAAABlDYWxsZXIgaXMgbm90IGF1dGhvcml6ZWQuAAAAAAAADFVuYXV0aG9yaXplZAAAAGYAAAAcU3RyZWFtIGlzIGFscmVhZHkgY2FuY2VsbGVkLgAAABBBbHJlYWR5Q2FuY2VsbGVkAAAAZwAAABlTdHJlYW0gaXMgbm90IGNhbmNlbGFibGUuAAAAAAAADU5vdENhbmNlbGFibGUAAAAAAABoAAAAKVdpdGhkcmF3IGFtb3VudCBleGNlZWRzIHVubG9ja2VkIGJhbGFuY2UuAAAAAAAACE92ZXJkcmF3AAAAaQAAADxJbnZhbGlkIHRpbWUgcGFyYW1ldGVycyAoc3RhcnQgPj0gZW5kLCBjbGlmZiBvdXRzaWRlIHJhbmdlKS4AAAAQSW52YWxpZFRpbWVSYW5nZQAAAGoAAAAZVG90YWwgYW1vdW50IG11c3QgYmUgPiAwLgAAAAAAAApBbW91bnRaZXJvAAAAAABrAAAAHUNvbnRyYWN0IGFscmVhZHkgaW5pdGlhbGl6ZWQuAAAAAAAAEkFscmVhZHlJbml0aWFsaXplZAAAAAAAbAAAAB1Db250cmFjdCBub3QgeWV0IGluaXRpYWxpemVkLgAAAAAAAA5Ob3RJbml0aWFsaXplZAAAAAAAbQAAADFTZW5kZXIgYW5kIHJlY2lwaWVudCBtdXN0IGJlIGRpZmZlcmVudCBhZGRyZXNzZXMuAAAAAAAAFVNlbmRlckVxdWFsc1JlY2lwaWVudAAAAAAAAG4AAABLU3RhcnQgYW5kIGNsaWZmIHVubG9jayBhbW91bnRzIG11c3QgYmUgbm9ubmVnYXRpdmUgYW5kIGZpdCBpbiB0b3RhbCBhbW91bnQuAAAAABNJbnZhbGlkVW5sb2NrQW1vdW50AAAAAG8AAAA+VGhlIHRva2VuIGRpZCBub3QgZGViaXQgYW5kIGNyZWRpdCB0aGUgZXhhY3QgcmVxdWVzdGVkIGFtb3VudC4AAAAAABVUb2tlblRyYW5zZmVyTWlzbWF0Y2gAAAAAAABw", + "AAAABAAAACdFcnJvcnMgc3BlY2lmaWMgdG8gdGhlIFJvdXRlciBjb250cmFjdC4AAAAAAAAAAAtSb3V0ZXJFcnJvcgAAAAAFAAAAAAAAABJBbHJlYWR5SW5pdGlhbGl6ZWQAAAAAAS0AAAAAAAAADk5vdEluaXRpYWxpemVkAAAAAAEuAAAAAAAAAA1Ob3RBdXRob3JpemVkAAAAAAABLwAAAAAAAAARSW52YWxpZFN0cmVhbVR5cGUAAAAAAAEwAAAAMENvcmUgY29udHJhY3QgYWRkcmVzc2VzIHdlcmUgYWxyZWFkeSBjb25maWd1cmVkLgAAABFBbHJlYWR5Q29uZmlndXJlZAAAAAAAATE=", + "AAAAAgAAAKFTdG9yYWdlIGtleXMgZm9yIGNvbnRyYWN0IGRhdGEuCgpVc2luZyBhIHR5cGVkIGVudW0gcHJldmVudHMga2V5IGNvbGxpc2lvbnMgKFNLSUxMLm1kIMKnMykuCktleXMgYXJlIG5hbWVzcGFjZWQgYnkgdmFyaWFudCB0byBrZWVwIGRpZmZlcmVudCBkYXRhIHR5cGVzIHNlcGFyYXRlLgAAAAAAAAAAAAAHRGF0YUtleQAAAAAPAAAAAAAAACFBZG1pbiBhZGRyZXNzIChJbnN0YW5jZSBzdG9yYWdlKS4AAAAAAAAFQWRtaW4AAAAAAAAAAAAAKk5leHQgc3RyZWFtIElEIGNvdW50ZXIgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAAADE5leHRTdHJlYW1JZAAAAAEAAAA+QSBGbG93IHN0cmVhbSByZWNvcmQsIGtleWVkIGJ5IHN0cmVhbSBJRCAoUGVyc2lzdGVudCBzdG9yYWdlKS4AAAAAAApGbG93U3RyZWFtAAAAAAABAAAABgAAAAEAAABAQSBMb2NrdXAgc3RyZWFtIHJlY29yZCwga2V5ZWQgYnkgc3RyZWFtIElEIChQZXJzaXN0ZW50IHN0b3JhZ2UpLgAAAAxMb2NrdXBTdHJlYW0AAAABAAAABgAAAAEAAACTQWdncmVnYXRlIHRva2VuIGJhbGFuY2UgaGVsZCBieSB0aGUgY29udHJhY3QgZm9yIGEgZ2l2ZW4gdG9rZW4gYWRkcmVzcy4KVXNlZCBmb3Igc3VycGx1cyByZWNvdmVyeSAvIGFjY291bnRpbmcgcmVjb25jaWxpYXRpb24gKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAABBBZ2dyZWdhdGVCYWxhbmNlAAAAAQAAABMAAAABAAAAOE5GVCB0b2tlbiBvd25lciwga2V5ZWQgYnkgdG9rZW4gSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAClRva2VuT3duZXIAAAAAAAEAAAALAAAAAQAAAExORlQgc3RyZWFtIGRhdGEgbWFwcGluZyB0b2tlbiBJRCB0byBzdHJlYW0gdHlwZSBhbmQgSUQgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAD1Rva2VuU3RyZWFtRGF0YQAAAAABAAAACwAAAAEAAABFSW1tdXRhYmxlIHBlci1zdHJlYW0gTkZUIHRyYW5zZmVyYWJpbGl0eSBwb2xpY3kgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAAAAAAEVRva2VuVHJhbnNmZXJhYmxlAAAAAAAAAQAAAAsAAAABAAAAOE51bWJlciBvZiBORlRzIG93bmVkIGJ5IGFuIGFkZHJlc3MgKFBlcnNpc3RlbnQgc3RvcmFnZSkuAAAACk5mdEJhbGFuY2UAAAAAAAEAAAATAAAAAQAAADtUb2tlbiBtZXRhZGF0YSwgZS5nLiwgbmFtZSwgc3ltYm9sLCBVUkkgKEluc3RhbmNlIHN0b3JhZ2UpLgAAAAANVG9rZW5NZXRhZGF0YQAAAAAAAAEAAAARAAAAAAAAACxSb3V0ZXIgY29uZmlndXJhdGlvbjogRmxvdyBjb250cmFjdCBhZGRyZXNzLgAAAAxGbG93Q29udHJhY3QAAAAAAAAALlJvdXRlciBjb25maWd1cmF0aW9uOiBMb2NrdXAgY29udHJhY3QgYWRkcmVzcy4AAAAAAA5Mb2NrdXBDb250cmFjdAAAAAAAAAAAACtSb3V0ZXIgY29uZmlndXJhdGlvbjogTkZUIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAtOZnRDb250cmFjdAAAAAAAAAAAO0NvcmUtZW5naW5lIGNvbmZpZ3VyYXRpb246IHRydXN0ZWQgUm91dGVyIGNvbnRyYWN0IGFkZHJlc3MuAAAAAAZSb3V0ZXIAAAAAAAAAAAA9UGF5bWFzdGVyIGNvbmZpZ3VyYXRpb246IGxpc3Qgb2YgYWxsb3dlZCBmZWUgdG9rZW4gYWRkcmVzc2VzLgAAAAAAABBBbGxvd2VkRmVlVG9rZW5z" ]), options ) } public readonly fromJSON = { - burn: this.txFromJSON, - mint: this.txFromJSON, + mint: this.txFromJSON, name: this.txFromJSON, symbol: this.txFromJSON, balance: this.txFromJSON, upgrade: this.txFromJSON, owner_of: this.txFromJSON, transfer: this.txFromJSON, - initialize: this.txFromJSON, - get_stream_data: this.txFromJSON + get_stream_data: this.txFromJSON, + is_transferable: this.txFromJSON } } diff --git a/src/index.ts b/src/index.ts index 6c8c8d2..2caecc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ /** Fundable Protocol multichain SDK. Stellar is the first implemented adapter. */ -export const VERSION = "0.1.0"; +export const VERSION = "0.2.0"; export * from "./client.js"; export * from "./core/index.js"; diff --git a/src/stellar/client.test.ts b/src/stellar/client.test.ts index 6d601b4..f15e217 100644 --- a/src/stellar/client.test.ts +++ b/src/stellar/client.test.ts @@ -1,5 +1,5 @@ import { StrKey } from "@stellar/stellar-sdk"; -import { describe, expect, it } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { FundableError } from "../core/index.js"; import { createFundableClient } from "../client.js"; @@ -33,7 +33,7 @@ describe("createFundableClient", () => { ).toThrow(FundableError); }); - it("enables optional Router, Stream NFT, and Paymaster capabilities", () => { + it("enables optional Lockup, Router, Stream NFT, and Paymaster capabilities", () => { const client = createFundableClient({ chain: "stellar", network: "testnet", @@ -41,6 +41,7 @@ describe("createFundableClient", () => { networkPassphrase: "Test SDF Network ; September 2015", contracts: { flow: contractId(), + lockup: contractId(), router: contractId(), streamNft: contractId(), paymaster: contractId(), @@ -48,7 +49,46 @@ describe("createFundableClient", () => { }); expect(client.router).toBeDefined(); + expect(client.lockups).toBeDefined(); expect(client.streamNft).toBeDefined(); expect(client.paymaster).toBeDefined(); }); + + it("signs a current sponsorship build with the configured wallet", async () => { + const signAuthEntry = vi.fn(async () => ({ + signedAuthEntry: "signed-entry", + signerAddress: "GACCOUNT", + })); + const client = createFundableClient({ + chain: "stellar", + network: "testnet", + rpcUrl: "https://rpc.example.com", + networkPassphrase: "Test SDF Network ; September 2015", + contracts: { flow: contractId() }, + publicKey: "GACCOUNT", + signAuthEntry, + sponsorship: { + backendUrl: "https://api.example.com", + accessToken: "session-token", + }, + }); + + await expect( + client.signSponsorshipAuthorization({ + transactionXdr: "built-xdr", + userAuthEntry: "auth-entry", + feeToken: "CFEE", + networkFeeStroops: "100", + estimatedFee: "123", + estimatedFeeUi: "0.0000123", + maximumFee: "130", + maximumFeeUi: "0.0000130", + validUntil: "2030-01-01T00:00:00.000Z", + }), + ).resolves.toBe("signed-entry"); + expect(signAuthEntry).toHaveBeenCalledWith("auth-entry", { + networkPassphrase: "Test SDF Network ; September 2015", + address: "GACCOUNT", + }); + }); }); diff --git a/src/stellar/client.ts b/src/stellar/client.ts index 24ae5eb..e55cde2 100644 --- a/src/stellar/client.ts +++ b/src/stellar/client.ts @@ -1,17 +1,23 @@ import { FUNDABLE_ERROR_CODES, FundableError } from "../core/index.js"; import { StellarFlowClient } from "./flow-client.js"; +import { StellarLockupClient } from "./lockup-client.js"; import { StellarPaymasterClient } from "./paymaster-client.js"; import { StellarRouterClient } from "./router-client.js"; import { StellarStreamNftClient } from "./stream-nft-client.js"; +import { StellarSponsorshipClient } from "./sponsorship-client.js"; +import type { StellarSponsorBuild } from "./sponsorship-client.js"; import type { StellarFundableClientConfig } from "./types.js"; import { assertContractId } from "./validation.js"; export class StellarFundableClient { readonly chain = "stellar" as const; readonly flows: StellarFlowClient; + readonly lockups?: StellarLockupClient; readonly router?: StellarRouterClient; readonly streamNft?: StellarStreamNftClient; + /** @deprecated Production integrations must use `sponsorship`. */ readonly paymaster?: StellarPaymasterClient; + readonly sponsorship?: StellarSponsorshipClient; constructor(readonly config: StellarFundableClientConfig) { if (!config.rpcUrl || !config.networkPassphrase) { @@ -24,6 +30,14 @@ export class StellarFundableClient { assertContractId(config.contracts.flow, "Flow contract"); this.flows = new StellarFlowClient(config); + if (config.contracts.lockup) { + assertContractId(config.contracts.lockup, "Lockup contract"); + this.lockups = new StellarLockupClient({ + ...config, + contracts: { ...config.contracts, lockup: config.contracts.lockup }, + }); + } + if (config.contracts.router) { assertContractId(config.contracts.router, "Router contract"); this.router = new StellarRouterClient({ @@ -45,5 +59,44 @@ export class StellarFundableClient { contracts: { ...config.contracts, paymaster: config.contracts.paymaster }, }); } + if (config.sponsorship) { + this.sponsorship = new StellarSponsorshipClient(config.sponsorship); + } + } + + async signSponsorshipAuthorization(build: StellarSponsorBuild): Promise { + if (!this.sponsorship) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.UNSUPPORTED_CAPABILITY, + message: "Stellar sponsorship is not configured.", + chain: "stellar", + }); + } + if (this.sponsorship.isBuildExpired(build)) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.AUTHORIZATION_EXPIRED, + message: "The sponsorship authorization has expired. Build it again before signing.", + chain: "stellar", + }); + } + if (!this.config.signAuthEntry) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.UNSUPPORTED_CAPABILITY, + message: "A wallet authorization-entry signer is required.", + chain: "stellar", + }); + } + const result = await this.config.signAuthEntry(build.userAuthEntry, { + networkPassphrase: this.config.networkPassphrase, + address: this.config.publicKey, + }); + if (result.error || !result.signedAuthEntry) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.TRANSACTION_FAILED, + message: result.error?.message ?? "The wallet did not sign the authorization entry.", + chain: "stellar", + }); + } + return result.signedAuthEntry; } } diff --git a/src/stellar/index.ts b/src/stellar/index.ts index 1e32bdd..595a129 100644 --- a/src/stellar/index.ts +++ b/src/stellar/index.ts @@ -1,6 +1,8 @@ export * from "./client.js"; export * from "./flow-client.js"; +export * from "./lockup-client.js"; export * from "./paymaster-client.js"; export * from "./router-client.js"; export * from "./stream-nft-client.js"; +export * from "./sponsorship-client.js"; export * from "./types.js"; diff --git a/src/stellar/lockup-client.ts b/src/stellar/lockup-client.ts new file mode 100644 index 0000000..20b2c1a --- /dev/null +++ b/src/stellar/lockup-client.ts @@ -0,0 +1,113 @@ +import { + LOCKUP_STATUSES, + toFundableError, + type LockupActorInput, + type LockupStatus, +} from "../core/index.js"; +import { + Client as GeneratedLockupClient, + LockupStatus as GeneratedLockupStatus, +} from "../generated/lockup/src/index.js"; +import type { StellarTransaction } from "./flow-client.js"; +import type { + StellarFundableClientConfig, + StellarMethodOptions, +} from "./types.js"; +import { assertStellarAddress, toStreamId } from "./validation.js"; + +const LOCKUP_STATUS_BY_CONTRACT_VALUE: Record = { + [GeneratedLockupStatus.Pending]: LOCKUP_STATUSES.PENDING, + [GeneratedLockupStatus.Streaming]: LOCKUP_STATUSES.STREAMING, + [GeneratedLockupStatus.Settled]: LOCKUP_STATUSES.SETTLED, + [GeneratedLockupStatus.Canceled]: LOCKUP_STATUSES.CANCELED, + [GeneratedLockupStatus.Depleted]: LOCKUP_STATUSES.DEPLETED, +}; + +export class StellarLockupClient { + private readonly client: GeneratedLockupClient; + + constructor( + config: StellarFundableClientConfig & { contracts: { lockup: string } }, + ) { + this.client = new GeneratedLockupClient({ + contractId: config.contracts.lockup, + networkPassphrase: config.networkPassphrase, + rpcUrl: config.rpcUrl, + publicKey: config.publicKey, + allowHttp: config.allowHttp, + headers: config.headers, + signTransaction: config.signTransaction, + signAuthEntry: config.signAuthEntry, + }); + } + + async cancel( + input: LockupActorInput, + options?: StellarMethodOptions, + ): Promise> { + assertStellarAddress(input.sender, "Lockup sender"); + return this.client.cancel( + { stream_id: toStreamId(input.streamId), sender: input.sender }, + options, + ); + } + + async renounce( + input: LockupActorInput, + options?: StellarMethodOptions, + ): Promise> { + assertStellarAddress(input.sender, "Lockup sender"); + return this.client.renounce( + { stream_id: toStreamId(input.streamId), sender: input.sender }, + options, + ); + } + + async statusOf(streamId: string | bigint): Promise { + try { + const transaction = await this.client.status_of({ + stream_id: toStreamId(streamId), + }); + const status = LOCKUP_STATUS_BY_CONTRACT_VALUE[transaction.result]; + if (!status) throw new Error(`Unknown Lockup status: ${transaction.result}`); + return status; + } catch (error) { + throw toFundableError(error, "Failed to load the Lockup status.", "stellar"); + } + } + + async isCancelable(streamId: string | bigint): Promise { + try { + const transaction = await this.client.is_cancelable({ + stream_id: toStreamId(streamId), + }); + return transaction.result; + } catch (error) { + throw toFundableError(error, "Failed to load Lockup cancellation state.", "stellar"); + } + } + + async withdrawableAmount(streamId: string | bigint): Promise { + return this.readAmount("withdrawable", streamId); + } + + async refundableAmount(streamId: string | bigint): Promise { + return this.readAmount("refundable", streamId); + } + + private async readAmount( + kind: "withdrawable" | "refundable", + streamId: string | bigint, + ): Promise { + try { + const args = { stream_id: toStreamId(streamId) }; + const transaction = + kind === "withdrawable" + ? await this.client.withdrawable_amount_of(args) + : await this.client.refundable_amount_of(args); + return transaction.result; + } catch (error) { + throw toFundableError(error, `Failed to load the ${kind} Lockup amount.`, "stellar"); + } + } +} diff --git a/src/stellar/paymaster-client.ts b/src/stellar/paymaster-client.ts index 7c3f979..83b78ec 100644 --- a/src/stellar/paymaster-client.ts +++ b/src/stellar/paymaster-client.ts @@ -9,6 +9,7 @@ import type { StellarFundableClientConfig, StellarMethodOptions } from "./types. import type { StellarTransaction } from "./flow-client.js"; import { assertPositive, assertStellarAddress } from "./validation.js"; +/** @deprecated The Fundable Paymaster is retained for migration only. */ export class StellarPaymasterClient { private readonly client: GeneratedPaymasterClient; diff --git a/src/stellar/protocol-clients.test.ts b/src/stellar/protocol-clients.test.ts index 40fb242..0c40e3e 100644 --- a/src/stellar/protocol-clients.test.ts +++ b/src/stellar/protocol-clients.test.ts @@ -6,13 +6,27 @@ const generated = vi.hoisted(() => ({ createFlow: vi.fn(), createLockup: vi.fn(), getStreamData: vi.fn(), + getStream: vi.fn(), + isTransferable: vi.fn(), + cancelLockup: vi.fn(), + lockupStatus: vi.fn(), forward: vi.fn(), })); vi.mock("../generated/router/src/index.js", () => ({ + CanonicalStreamStatus: { + Pending: 0, + Active: 1, + Paused: 2, + Canceled: 3, + Completed: 4, + Failed: 5, + }, + StreamType: { Flow: 0, Lockup: 1 }, Client: class { create_flow_stream = generated.createFlow; create_lockup_stream = generated.createLockup; + get_stream = generated.getStream; }, })); @@ -20,6 +34,7 @@ vi.mock("../generated/stream_nft/src/index.js", () => ({ StreamType: { Flow: 0, Lockup: 1 }, Client: class { get_stream_data = generated.getStreamData; + is_transferable = generated.isTransferable; }, })); @@ -29,7 +44,22 @@ vi.mock("../generated/paymaster/src/index.js", () => ({ }, })); +vi.mock("../generated/lockup/src/index.js", () => ({ + LockupStatus: { + Pending: 0, + Streaming: 1, + Settled: 2, + Canceled: 3, + Depleted: 4, + }, + Client: class { + cancel = generated.cancelLockup; + status_of = generated.lockupStatus; + }, +})); + import { StellarPaymasterClient } from "./paymaster-client.js"; +import { StellarLockupClient } from "./lockup-client.js"; import { StellarRouterClient } from "./router-client.js"; import { StellarStreamNftClient } from "./stream-nft-client.js"; @@ -76,6 +106,8 @@ describe("Stellar protocol clients", () => { rate_per_second: 10n ** 18n, token_decimals: 7, start_time: 123n, + initial_amount: 0n, + transferable: false, }, undefined, ); @@ -103,6 +135,7 @@ describe("Stellar protocol clients", () => { cliffUnlockAmount: 20n, granularitySeconds: 60n, cancelable: true, + transferable: true, }); expect(generated.createLockup).toHaveBeenCalledWith( @@ -120,6 +153,7 @@ describe("Stellar protocol clients", () => { granularity: 60n, cancelable: true, }, + transferable: true, }, undefined, ); @@ -159,6 +193,60 @@ describe("Stellar protocol clients", () => { }); }); + it("loads canonical Router metadata by public NFT token ID", async () => { + const owner = Keypair.random().publicKey(); + generated.getStream.mockResolvedValue({ + result: { + token_id: 7n, + core_stream_id: 42n, + stream_type: 1, + status: 3, + owner, + transferable: true, + }, + }); + const client = new StellarRouterClient({ + ...baseConfig, + contracts: { ...baseConfig.contracts, router: contractId(1) }, + }); + + await expect(client.getStream("7")).resolves.toEqual({ + tokenId: "7", + coreStreamId: "42", + streamKind: "lockup", + status: "canceled", + owner, + transferable: true, + }); + }); + + it("reads immutable NFT transferability", async () => { + generated.isTransferable.mockResolvedValue({ result: false }); + const client = new StellarStreamNftClient({ + ...baseConfig, + contracts: { ...baseConfig.contracts, streamNft: contractId(3) }, + }); + + await expect(client.isTransferable("7")).resolves.toBe(false); + }); + + it("routes sender cancellation through the Lockup core contract", async () => { + const sender = Keypair.random().publicKey(); + generated.cancelLockup.mockResolvedValue({ result: 50n }); + generated.lockupStatus.mockResolvedValue({ result: 3 }); + const client = new StellarLockupClient({ + ...baseConfig, + contracts: { ...baseConfig.contracts, lockup: contractId(4) }, + }); + + await client.cancel({ streamId: "42", sender }); + await expect(client.statusOf("42")).resolves.toBe("canceled"); + expect(generated.cancelLockup).toHaveBeenCalledWith( + { stream_id: 42n, sender }, + undefined, + ); + }); + it("maps a bounded Paymaster forward request", async () => { const user = Keypair.random().publicKey(); const recipient = Keypair.random().publicKey(); diff --git a/src/stellar/router-client.ts b/src/stellar/router-client.ts index 55231eb..fa96ded 100644 --- a/src/stellar/router-client.ts +++ b/src/stellar/router-client.ts @@ -1,12 +1,21 @@ import { FUNDABLE_ERROR_CODES, FundableError, + CANONICAL_STREAM_STATUSES, + STREAM_KINDS, + toFundableError, toUnixSeconds, type CreateFlowInput, type CreateLockupInput, type RouterWithdrawInput, + type CanonicalStreamStatus, + type StreamMetadata, } from "../core/index.js"; -import { Client as GeneratedRouterClient } from "../generated/router/src/index.js"; +import { + CanonicalStreamStatus as GeneratedCanonicalStreamStatus, + Client as GeneratedRouterClient, + StreamType as GeneratedStreamType, +} from "../generated/router/src/index.js"; import type { StellarFundableClientConfig, StellarMethodOptions } from "./types.js"; import type { StellarTransaction } from "./flow-client.js"; import { @@ -17,6 +26,15 @@ import { toTokenId, } from "./validation.js"; +const CANONICAL_STATUS_BY_CONTRACT_VALUE: Record = { + [GeneratedCanonicalStreamStatus.Pending]: CANONICAL_STREAM_STATUSES.PENDING, + [GeneratedCanonicalStreamStatus.Active]: CANONICAL_STREAM_STATUSES.ACTIVE, + [GeneratedCanonicalStreamStatus.Paused]: CANONICAL_STREAM_STATUSES.PAUSED, + [GeneratedCanonicalStreamStatus.Canceled]: CANONICAL_STREAM_STATUSES.CANCELED, + [GeneratedCanonicalStreamStatus.Completed]: CANONICAL_STREAM_STATUSES.COMPLETED, + [GeneratedCanonicalStreamStatus.Failed]: CANONICAL_STREAM_STATUSES.FAILED, +}; + export class StellarRouterClient { private readonly client: GeneratedRouterClient; @@ -51,6 +69,8 @@ export class StellarRouterClient { rate_per_second: input.ratePerSecond, token_decimals: input.token.decimals, start_time: toUnixSeconds(input.startTime), + initial_amount: input.initialAmount ?? 0n, + transferable: input.transferable ?? false, }, options, ); @@ -121,6 +141,7 @@ export class StellarRouterClient { granularity, cancelable: input.cancelable ?? false, }, + transferable: input.transferable ?? false, }, options, ); @@ -159,4 +180,72 @@ export class StellarRouterClient { options, ); } + + async ownerOf(tokenId: string | bigint): Promise { + try { + const transaction = await this.client.owner_of({ token_id: toTokenId(tokenId) }); + return transaction.result; + } catch (error) { + throw toFundableError(error, "Failed to load the current stream owner.", "stellar"); + } + } + + async statusOf(tokenId: string | bigint): Promise { + try { + const transaction = await this.client.status_of({ token_id: toTokenId(tokenId) }); + const status = CANONICAL_STATUS_BY_CONTRACT_VALUE[transaction.result]; + if (!status) throw new Error(`Unknown canonical stream status: ${transaction.result}`); + return status; + } catch (error) { + throw toFundableError(error, "Failed to load the canonical stream status.", "stellar"); + } + } + + async coreStreamId(tokenId: string | bigint): Promise { + try { + const transaction = await this.client.core_stream_id({ token_id: toTokenId(tokenId) }); + return transaction.result.toString(); + } catch (error) { + throw toFundableError(error, "Failed to load the core stream ID.", "stellar"); + } + } + + async getStream(tokenId: string | bigint): Promise { + try { + const transaction = await this.client.get_stream({ token_id: toTokenId(tokenId) }); + const metadata = transaction.result; + const status = CANONICAL_STATUS_BY_CONTRACT_VALUE[metadata.status]; + if (!status) throw new Error(`Unknown canonical stream status: ${metadata.status}`); + if ( + metadata.stream_type !== GeneratedStreamType.Flow && + metadata.stream_type !== GeneratedStreamType.Lockup + ) { + throw new Error(`Unknown stream type: ${metadata.stream_type}`); + } + return { + tokenId: metadata.token_id.toString(), + coreStreamId: metadata.core_stream_id.toString(), + streamKind: + metadata.stream_type === GeneratedStreamType.Flow + ? STREAM_KINDS.FLOW + : STREAM_KINDS.LOCKUP, + status, + owner: metadata.owner, + transferable: metadata.transferable, + }; + } catch (error) { + throw toFundableError(error, "Failed to load stream metadata.", "stellar"); + } + } + + async voidFlow( + input: { tokenId: string | bigint; caller: string }, + options?: StellarMethodOptions, + ): Promise> { + assertStellarAddress(input.caller, "Caller"); + return this.client.void_flow( + { token_id: toTokenId(input.tokenId), caller: input.caller }, + options, + ); + } } diff --git a/src/stellar/sponsorship-client.test.ts b/src/stellar/sponsorship-client.test.ts new file mode 100644 index 0000000..305823a --- /dev/null +++ b/src/stellar/sponsorship-client.test.ts @@ -0,0 +1,160 @@ +import { describe, expect, it, vi } from "vitest"; +import { FundableError } from "../core/index.js"; +import { StellarSponsorshipClient } from "./sponsorship-client.js"; + +function jsonResponse(body: object, status = 200): Response { + return new Response(JSON.stringify(body), { + status, + headers: { "Content-Type": "application/json" }, + }); +} + +describe("StellarSponsorshipClient", () => { + it("authenticates a wallet and uses the bearer session for sponsorship", async () => { + const fetcher = vi + .fn() + .mockResolvedValueOnce( + jsonResponse({ + challenge_id: "challenge-id", + message: "Sign this Fundable challenge", + expires_at: "2030-01-01T00:00:00.000Z", + }), + ) + .mockResolvedValueOnce( + jsonResponse({ + access_token: "session-token", + token_type: "Bearer", + expires_at: "2030-01-01T00:15:00.000Z", + wallet: { address: "GACCOUNT", network: "TESTNET" }, + }), + ) + .mockResolvedValueOnce( + jsonResponse({ + fee_token: "CFEE", + fee_in_token: "123", + fee_in_token_ui: "0.0000123", + conversion_rate: "1", + max_fee_in_token: "130", + max_fee_in_token_ui: "0.0000130", + }), + ); + const client = new StellarSponsorshipClient({ + backendUrl: "https://api.example.com/", + fetch: fetcher, + headers: { "X-Fingerprint": "browser-fingerprint" }, + }); + const sign = vi.fn(async () => "base64-signature"); + + await client.authenticate("GACCOUNT", "TESTNET", sign); + const quote = await client.quote("unsigned-xdr", "TESTNET"); + + expect(sign).toHaveBeenCalledWith("Sign this Fundable challenge"); + expect(quote.maximumFee).toBe("130"); + expect(fetcher).toHaveBeenNthCalledWith( + 3, + "https://api.example.com/api/payment-streams/sponsor/quote", + expect.objectContaining({ + headers: expect.objectContaining({ + Authorization: "Bearer session-token", + "X-Fingerprint": "browser-fingerprint", + }), + }), + ); + }); + + it("maps a build and submits it with an idempotency key", async () => { + const fetcher = vi + .fn() + .mockResolvedValueOnce( + jsonResponse({ + transaction_xdr: "built-xdr", + user_auth_entry: "auth-entry", + fee_token: "CFEE", + fee_in_stroops: "100", + fee_in_token: "123", + fee_in_token_ui: "0.0000123", + max_fee_in_token: "130", + max_fee_in_token_ui: "0.0000130", + valid_until: "2030-01-01T00:00:00.000Z", + }), + ) + .mockResolvedValueOnce( + jsonResponse({ + intentId: "intent-1", + submissionId: "submission-1", + status: "pending", + relayerTransactionId: "relayer-1", + transactionHash: "tx-hash", + streamId: "7", + reused: false, + }), + ); + const client = new StellarSponsorshipClient({ + backendUrl: "https://api.example.com", + accessToken: "session-token", + fetch: fetcher, + }); + const build = await client.build("unsigned-xdr", "TESTNET"); + const intent = { + operation: "cancel" as const, + contract: "CLOCKUP", + stream_id: "42", + sender: "GACCOUNT", + }; + + const result = await client.submit({ + build, + signedAuthEntry: "signed-auth-entry", + network: "TESTNET", + intent, + idempotencyKey: "request-1", + }); + + expect(result.streamTokenId).toBe("7"); + expect(result.transactionHash).toBe("tx-hash"); + expect(fetcher).toHaveBeenNthCalledWith( + 2, + "https://api.example.com/api/payment-streams/sponsor/submit", + expect.objectContaining({ + headers: expect.objectContaining({ "Idempotency-Key": "request-1" }), + }), + ); + }); + + it("rejects expired builds before calling the backend", async () => { + const fetcher = vi.fn(); + const client = new StellarSponsorshipClient({ + backendUrl: "https://api.example.com", + accessToken: "session-token", + fetch: fetcher, + }); + + await expect( + client.submit({ + build: { + transactionXdr: "built-xdr", + userAuthEntry: "auth-entry", + feeToken: "CFEE", + networkFeeStroops: "100", + estimatedFee: "123", + estimatedFeeUi: "0.0000123", + maximumFee: "130", + maximumFeeUi: "0.0000130", + validUntil: "2020-01-01T00:00:00.000Z", + }, + signedAuthEntry: "signed-auth-entry", + network: "TESTNET", + intent: { + operation: "pause", + contract: "CFLOW", + stream_id: "42", + sender: "GACCOUNT", + }, + idempotencyKey: "request-1", + }), + ).rejects.toMatchObject>({ + code: "AUTHORIZATION_EXPIRED", + }); + expect(fetcher).not.toHaveBeenCalled(); + }); +}); diff --git a/src/stellar/sponsorship-client.ts b/src/stellar/sponsorship-client.ts new file mode 100644 index 0000000..14dd5cf --- /dev/null +++ b/src/stellar/sponsorship-client.ts @@ -0,0 +1,336 @@ +import { + FUNDABLE_ERROR_CODES, + FundableError, +} from "../core/index.js"; + +export type StellarSponsorshipNetwork = "MAINNET" | "TESTNET"; + +export interface StellarWalletChallenge { + challengeId: string; + message: string; + expiresAt: string; +} + +export interface StellarWalletSession { + accessToken: string; + tokenType: "Bearer"; + expiresAt: string; + wallet: { + address: string; + network: StellarSponsorshipNetwork; + }; +} + +export interface StellarSponsorQuote { + feeToken: string; + estimatedFee: string; + estimatedFeeUi: string; + conversionRate: string; + maximumFee: string | null; + maximumFeeUi: string | null; +} + +export interface StellarSponsorBuild { + transactionXdr: string; + userAuthEntry: string; + feeToken: string; + networkFeeStroops: string; + estimatedFee: string; + estimatedFeeUi: string; + maximumFee: string; + maximumFeeUi: string; + validUntil: string; +} + +interface ExistingStreamIntent { + contract: string; + stream_id: string; +} + +export type StellarSponsorIntent = + | { + operation: "create"; + stream_kind: "lockup"; + sender: string; + recipient: string; + token: string; + start_time: number; + transferable: boolean; + total_amount: string; + end_time: number; + cliff_time: number; + start_unlock_amount: string; + cliff_unlock_amount: string; + granularity: number; + cancelable: boolean; + } + | { + operation: "create"; + stream_kind: "flow"; + sender: string; + recipient: string; + token: string; + start_time: number; + transferable: boolean; + rate_per_second: string; + token_decimals: number; + initial_amount: string; + } + | (ExistingStreamIntent & { + operation: "withdraw"; + caller: string; + to: string; + withdraw_max: boolean; + amount?: string; + }) + | (ExistingStreamIntent & { operation: "cancel"; sender: string }) + | (ExistingStreamIntent & { + operation: "deposit"; + funder: string; + amount: string; + }) + | (ExistingStreamIntent & { operation: "pause"; sender: string }) + | (ExistingStreamIntent & { + operation: "restart"; + sender: string; + rate_per_second: string; + }) + | (ExistingStreamIntent & { + operation: "adjust_rate"; + sender: string; + new_rate: string; + }) + | (ExistingStreamIntent & { + operation: "refund"; + sender: string; + refund_max: boolean; + amount?: string; + }) + | (ExistingStreamIntent & { operation: "void"; caller: string }); + +export interface StellarSponsorSubmission { + intentId: string; + submissionId: string; + status: string; + relayerTransactionId: string | null; + transactionHash: string | null; + streamTokenId: string | null; + reused: boolean; +} + +export interface StellarSponsorshipClientConfig { + backendUrl: string; + accessToken?: string; + headers?: Record; + fetch?: typeof fetch; +} + +type SignChallenge = (message: string) => Promise; + +function requiredString(value: unknown, field: string): string { + if (typeof value !== "string" || !value.trim()) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.API_REQUEST_FAILED, + message: `The backend returned an invalid ${field}.`, + chain: "stellar", + }); + } + return value; +} + +function optionalString(value: unknown): string | null { + return typeof value === "string" && value.trim() ? value : null; +} + +export class StellarSponsorshipClient { + private readonly fetcher: typeof fetch; + private readonly backendUrl: string; + private readonly headers: Record; + private accessToken?: string; + + constructor(config: StellarSponsorshipClientConfig) { + if (!config.backendUrl?.trim()) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.INVALID_CONFIGURATION, + message: "A backend URL is required for Stellar sponsorship.", + chain: "stellar", + }); + } + this.backendUrl = config.backendUrl.replace(/\/$/, ""); + this.accessToken = config.accessToken; + this.headers = config.headers ?? {}; + this.fetcher = config.fetch ?? fetch; + } + + setAccessToken(accessToken: string | undefined): void { + this.accessToken = accessToken?.trim() || undefined; + } + + async createChallenge( + address: string, + network: StellarSponsorshipNetwork, + ): Promise { + const payload = await this.request("/api/auth/wallet/challenge", { + method: "POST", + body: { address, network }, + authenticated: false, + }); + return { + challengeId: requiredString(payload.challenge_id, "challenge ID"), + message: requiredString(payload.message, "challenge message"), + expiresAt: requiredString(payload.expires_at, "challenge expiration"), + }; + } + + async authenticate( + address: string, + network: StellarSponsorshipNetwork, + signChallenge: SignChallenge, + ): Promise { + const challenge = await this.createChallenge(address, network); + const signature = await signChallenge(challenge.message); + const payload = await this.request("/api/auth/wallet/verify", { + method: "POST", + body: { challenge_id: challenge.challengeId, signature }, + authenticated: false, + }); + const accessToken = requiredString(payload.access_token, "access token"); + const session: StellarWalletSession = { + accessToken, + tokenType: "Bearer", + expiresAt: requiredString(payload.expires_at, "session expiration"), + wallet: { + address: requiredString(payload.wallet?.address, "wallet address"), + network: requiredString(payload.wallet?.network, "wallet network") as StellarSponsorshipNetwork, + }, + }; + this.accessToken = accessToken; + return session; + } + + async quote( + transactionXdr: string, + network: StellarSponsorshipNetwork, + ): Promise { + const payload = await this.sponsorRequest("quote", { transaction_xdr: transactionXdr, network }); + return { + feeToken: requiredString(payload.fee_token, "fee token"), + estimatedFee: requiredString(payload.fee_in_token, "estimated fee"), + estimatedFeeUi: requiredString(payload.fee_in_token_ui, "display fee"), + conversionRate: requiredString(payload.conversion_rate, "conversion rate"), + maximumFee: optionalString(payload.max_fee_in_token), + maximumFeeUi: optionalString(payload.max_fee_in_token_ui), + }; + } + + async build( + transactionXdr: string, + network: StellarSponsorshipNetwork, + ): Promise { + const payload = await this.sponsorRequest("build", { transaction_xdr: transactionXdr, network }); + return { + transactionXdr: requiredString(payload.transaction_xdr, "built transaction XDR"), + userAuthEntry: requiredString(payload.user_auth_entry, "user authorization entry"), + feeToken: requiredString(payload.fee_token, "fee token"), + networkFeeStroops: requiredString(payload.fee_in_stroops, "network fee"), + estimatedFee: requiredString(payload.fee_in_token, "estimated fee"), + estimatedFeeUi: requiredString(payload.fee_in_token_ui, "display fee"), + maximumFee: requiredString(payload.max_fee_in_token, "maximum fee"), + maximumFeeUi: requiredString(payload.max_fee_in_token_ui, "display maximum fee"), + validUntil: requiredString(payload.valid_until, "authorization expiration"), + }; + } + + async submit(input: { + build: StellarSponsorBuild; + signedAuthEntry: string; + network: StellarSponsorshipNetwork; + intent: StellarSponsorIntent; + idempotencyKey: string; + }): Promise { + if (this.isBuildExpired(input.build)) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.AUTHORIZATION_EXPIRED, + message: "The sponsorship authorization has expired. Build it again before submitting.", + chain: "stellar", + }); + } + const payload = await this.request("/api/payment-streams/sponsor/submit", { + method: "POST", + body: { + transaction_xdr: input.build.transactionXdr, + signed_auth_entry: input.signedAuthEntry, + network: input.network, + valid_until: input.build.validUntil, + intent: input.intent, + }, + authenticated: true, + headers: { "Idempotency-Key": input.idempotencyKey }, + }); + return { + intentId: requiredString(payload.intentId, "intent ID"), + submissionId: requiredString(payload.submissionId, "submission ID"), + status: requiredString(payload.status, "submission status"), + relayerTransactionId: optionalString(payload.relayerTransactionId), + transactionHash: optionalString(payload.transactionHash), + streamTokenId: optionalString(payload.streamId), + reused: payload.reused === true, + }; + } + + isBuildExpired(build: Pick, now = new Date()): boolean { + const validUntil = Date.parse(build.validUntil); + return !Number.isFinite(validUntil) || validUntil <= now.getTime(); + } + + private sponsorRequest(path: "quote" | "build", body: object): Promise> { + return this.request(`/api/payment-streams/sponsor/${path}`, { + method: "POST", + body, + authenticated: true, + }); + } + + private async request( + path: string, + options: { + method: "POST"; + body: object; + authenticated: boolean; + headers?: Record; + }, + ): Promise> { + if (options.authenticated && !this.accessToken) { + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.AUTHENTICATION_REQUIRED, + message: "Authenticate the Stellar wallet before requesting sponsorship.", + chain: "stellar", + }); + } + const response = await this.fetcher(`${this.backendUrl}${path}`, { + method: options.method, + headers: { + "Content-Type": "application/json", + ...this.headers, + ...options.headers, + ...(options.authenticated + ? { Authorization: `Bearer ${this.accessToken}` } + : {}), + }, + body: JSON.stringify(options.body), + }); + const payload = (await response.json().catch(() => ({}))) as Record; + if (!response.ok) { + const message = + (Array.isArray(payload.message) ? payload.message.join("; ") : payload.message) ?? + payload.error ?? + `Backend request failed with status ${response.status}.`; + throw new FundableError({ + code: FUNDABLE_ERROR_CODES.API_REQUEST_FAILED, + message: String(message), + chain: "stellar", + }); + } + return payload; + } +} diff --git a/src/stellar/stream-nft-client.ts b/src/stellar/stream-nft-client.ts index 5249510..8ae6cb6 100644 --- a/src/stellar/stream-nft-client.ts +++ b/src/stellar/stream-nft-client.ts @@ -71,6 +71,21 @@ export class StellarStreamNftClient { } } + async isTransferable(tokenId: string | bigint): Promise { + try { + const transaction = await this.client.is_transferable({ + token_id: toTokenId(tokenId), + }); + return transaction.result; + } catch (error) { + throw toFundableError( + error, + "Failed to load stream NFT transferability.", + "stellar", + ); + } + } + async transfer( input: TransferStreamNftInput, options?: StellarMethodOptions, diff --git a/src/stellar/types.ts b/src/stellar/types.ts index e4fb9ef..2d00232 100644 --- a/src/stellar/types.ts +++ b/src/stellar/types.ts @@ -2,6 +2,7 @@ import type { ClientOptions as ContractClientOptions, MethodOptions, } from "@stellar/stellar-sdk/contract"; +import type { StellarSponsorshipClientConfig } from "./sponsorship-client.js"; export const STELLAR_NETWORKS = { PUBLIC: "public", @@ -32,6 +33,7 @@ export interface StellarFundableClientConfig { headers?: Record; signTransaction?: ContractClientOptions["signTransaction"]; signAuthEntry?: ContractClientOptions["signAuthEntry"]; + sponsorship?: StellarSponsorshipClientConfig; } export type StellarMethodOptions = MethodOptions; From e45ba7c21e33146b0d0f85ebc00f531035720837 Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Sep 2026 12:34:02 +0100 Subject: [PATCH 2/2] test: target readiness testnet deployment --- src/stellar/testnet.integration.test.ts | 80 +++++++++++-------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/stellar/testnet.integration.test.ts b/src/stellar/testnet.integration.test.ts index c641b7d..d8f6789 100644 --- a/src/stellar/testnet.integration.test.ts +++ b/src/stellar/testnet.integration.test.ts @@ -1,4 +1,4 @@ -import { Asset, Keypair, Networks } from "@stellar/stellar-sdk"; +import { Networks } from "@stellar/stellar-sdk"; import { describe, expect, it } from "vitest"; import { createFundableClient } from "../client.js"; @@ -8,21 +8,25 @@ const deployment = { admin: process.env.FUNDABLE_TESTNET_ADMIN ?? "GDZJSPRSBTAJPAQ4NG6Y2ZCWHEX5HMS253TYVNAQJRPJHY27JPOHBIPZ", + owner: + process.env.FUNDABLE_TESTNET_STREAM_OWNER ?? + "GA4F3SQXOA6JETFYL4SG5JX7KGKDIU7RGFPUD3RNZTERVQODYUHYDACN", flow: process.env.FUNDABLE_TESTNET_FLOW ?? - "CCX3BJBDAORZI2QI7LS44RBJ7OT7HYZBXIXKC7UVMGS5SV766UUKRM2B", + "CAD57D33XJAHR7LSJVJU3MCFU3UMU72NK56GGLWDIKYZDSZICILSUKCY", + lockup: + process.env.FUNDABLE_TESTNET_LOCKUP ?? + "CBJRYJRQ24LP4DKKTUSVPCICLMMXIG7ZW5M4M7VNJUXGKDPBJ322ADT2", router: process.env.FUNDABLE_TESTNET_ROUTER ?? - "CCKPJ3QNP7M2OY3PIMYSGBKY3PVPTZXHRW6RBV5EMWKRZQ2P64JRFVCP", + "CAWZ5DGA6DTNG6GAF4O534SOP277JKZ6URTP3EE2KTBC3RM4YR4PQD7J", streamNft: process.env.FUNDABLE_TESTNET_STREAM_NFT ?? - "CBKWSE5M6PJ3JYYMMPWKNZ4T6H6EM77QZIQQFNHCRCECAP53ZKCETR5P", - paymaster: - process.env.FUNDABLE_TESTNET_PAYMASTER ?? - "CD4DQI4RKCPHGNROBXS4QWNGAHMYDTJK3GEGJZWLQFAWAVO6CYPSIXMM", + "CCYMOIEL3ID55C4DFQAGZEGJT4KEXM5OHIRJROZRLTLAO3EMSSHJIGLY", + tokenId: process.env.FUNDABLE_TESTNET_STREAM_TOKEN_ID ?? "1", }; -describe.runIf(runIntegration)("Fundable tagged testnet deployment", () => { +describe.runIf(runIntegration)("Fundable mainnet-readiness testnet deployment", () => { const client = createFundableClient({ chain: "stellar", network: "testnet", @@ -33,53 +37,37 @@ describe.runIf(runIntegration)("Fundable tagged testnet deployment", () => { publicKey: deployment.admin, contracts: { flow: deployment.flow, + lockup: deployment.lockup, router: deployment.router, streamNft: deployment.streamNft, - paymaster: deployment.paymaster, }, }); - it("reads Stream NFT and Paymaster state through the public clients", async () => { - await expect(client.streamNft?.balanceOf(deployment.admin)).resolves.toEqual( - expect.any(BigInt), + it("reads canonical Router metadata by public NFT token ID", async () => { + await expect(client.router?.getStream(deployment.tokenId)).resolves.toEqual( + expect.objectContaining({ + tokenId: deployment.tokenId, + coreStreamId: "1", + streamKind: "lockup", + owner: deployment.owner, + transferable: true, + }), ); - await expect( - client.paymaster?.isFeeTokenAllowed( - Asset.native().contractId(Networks.TESTNET), - ), - ).resolves.toEqual(expect.any(Boolean)); }); - it("simulates Router flow creation without submitting a transaction", async () => { - const transaction = await client.router?.createFlow({ - sender: deployment.admin, - recipient: Keypair.random().publicKey(), - token: { - address: Asset.native().contractId(Networks.TESTNET), - decimals: 7, - }, - ratePerSecond: 1n, - startTime: BigInt(Math.floor(Date.now() / 1_000) + 30), - }); - - expect(transaction?.result).toEqual(expect.any(BigInt)); + it("reads Stream NFT ownership and transferability", async () => { + await expect(client.streamNft?.ownerOf(deployment.tokenId)).resolves.toBe( + deployment.owner, + ); + await expect( + client.streamNft?.isTransferable(deployment.tokenId), + ).resolves.toBe(true); }); - it("simulates Router Lockup creation without submitting a transaction", async () => { - const startTime = BigInt(Math.floor(Date.now() / 1_000) + 30); - const transaction = await client.router?.createLockup({ - sender: deployment.admin, - recipient: Keypair.random().publicKey(), - token: { - address: Asset.native().contractId(Networks.TESTNET), - decimals: 7, - }, - totalAmount: 1n, - startTime, - endTime: startTime + 3_600n, - granularitySeconds: 1n, - }); - - expect(transaction?.result).toEqual(expect.any(BigInt)); + it("reads the underlying Lockup lifecycle through the core mapping", async () => { + const coreStreamId = await client.router?.coreStreamId(deployment.tokenId); + await expect(client.lockups?.statusOf(coreStreamId!)).resolves.toMatch( + /^(pending|streaming|settled|canceled|depleted)$/, + ); }); });