From 70353ff2684eef9e8efed4e4a07240c2cb4034ed Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 22 Oct 2026 11:07:02 -0400 Subject: [PATCH 1/2] chore(contract): sync ocp protos to 98cf14de --- Sources/OCPClientProtocol/ContractInfo.swift | 2 +- ...action_v1_ocp_transaction_service.pb.swift | 28 ++++++++++++++++--- ocp.lock | 4 +-- .../v1/ocp_transaction_service.proto | 14 ++++++++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Sources/OCPClientProtocol/ContractInfo.swift b/Sources/OCPClientProtocol/ContractInfo.swift index f062429..5d04b2f 100644 --- a/Sources/OCPClientProtocol/ContractInfo.swift +++ b/Sources/OCPClientProtocol/ContractInfo.swift @@ -2,7 +2,7 @@ public enum OCPContractInfo { public static let version = "0.8.0-dev" - public static let protoCommit = "6f70c0d8e832531fd006bbb02fcb2c5d07e0c247" + public static let protoCommit = "98cf14de185d5abadf6f03b6802ff298f61610d0" public static var isLocal: Bool { protoCommit == localSentinel } diff --git a/Sources/OCPClientProtocol/transaction_v1_ocp_transaction_service.pb.swift b/Sources/OCPClientProtocol/transaction_v1_ocp_transaction_service.pb.swift index b7a1edb..cb90db7 100644 --- a/Sources/OCPClientProtocol/transaction_v1_ocp_transaction_service.pb.swift +++ b/Sources/OCPClientProtocol/transaction_v1_ocp_transaction_service.pb.swift @@ -1038,6 +1038,11 @@ public struct Ocp_Transaction_V1_StatefulSwapRequest: Sendable { /// CanWithdrawToAccountResponse.fee_amount public var feeAmount: UInt64 = 0 + /// The CoinbaseStableSwapper swap instruction version the client will + /// use when constructing the transaction. 2 uses SwapV2. Any other value, + /// including unset, uses the legacy Swap instruction. + public var swapVersion: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1462,7 +1467,7 @@ public struct Ocp_Transaction_V1_StatefulSwapResponse: Sendable { /// 5. AssociatedTokenAccount::CreateIdempotent (open swap authority's from_mint ATA) /// 6. AssociatedTokenAccount::CreateIdempotent (open destination owner's to_mint ATA) /// 7. VM::TransferForSwapWithFee (from_mint VM swap ATA -> swap authority's from_mint ATA (swap amount) and fee destination (fee amount)) - /// 8. CoinbaseStableSwapper::Swap (from_mint swap authority ATA -> to_mint destination owner ATA) + /// 8. CoinbaseStableSwapper::Swap or CoinbaseStableSwapper::SwapV2, per client swap_version (from_mint swap authority ATA -> to_mint destination owner ATA) /// 9. Token::CloseAccount (closes swap authority's from_mint ATA) public struct CoinbaseStableSwapperServerParameter: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -1766,6 +1771,11 @@ public struct Ocp_Transaction_V1_StatelessSwapRequest: Sendable { /// The amount to swap from the source mint in quarks. public var swapAmount: UInt64 = 0 + /// The CoinbaseStableSwapper swap instruction version the client will + /// use when constructing the transaction. 2 uses SwapV2. Any other value, + /// including unset, uses the legacy Swap instruction. + public var swapVersion: UInt32 = 0 + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -1868,7 +1878,7 @@ public struct Ocp_Transaction_V1_StatelessSwapResponse: Sendable { /// 2. [Optional] ComputeBudget::SetComputeUnitPrice /// 3. [Optional] Memo::Memo /// 4. AssociatedTokenAccount::CreateIdempotent (open owner's to_mint VM Deposit ATA) - /// 5. CoinbaseStableSwapper::Swap (owner's from_mint ATA -> owner's to_mint VM Deposit ATA) + /// 5. CoinbaseStableSwapper::Swap or CoinbaseStableSwapper::SwapV2, per client swap_version (owner's from_mint ATA -> owner's to_mint VM Deposit ATA) public struct CoinbaseStableSwapperServerParameter: Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -4756,7 +4766,7 @@ extension Ocp_Transaction_V1_StatefulSwapRequest.Initiate.ReserveSwapClientParam extension Ocp_Transaction_V1_StatefulSwapRequest.Initiate.CoinbaseStableSwapperClientParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Ocp_Transaction_V1_StatefulSwapRequest.Initiate.protoMessageName + ".CoinbaseStableSwapperClientParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}from_mint\0\u{3}to_mint\0\u{3}swap_amount\0\u{3}funding_source\0\u{3}funding_id\0\u{3}destination_owner\0\u{3}fee_amount\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}from_mint\0\u{3}to_mint\0\u{3}swap_amount\0\u{3}funding_source\0\u{3}funding_id\0\u{3}destination_owner\0\u{3}fee_amount\0\u{3}swap_version\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -4772,6 +4782,7 @@ extension Ocp_Transaction_V1_StatefulSwapRequest.Initiate.CoinbaseStableSwapperC case 6: try { try decoder.decodeSingularStringField(value: &self.fundingID) }() case 7: try { try decoder.decodeSingularMessageField(value: &self._destinationOwner) }() case 8: try { try decoder.decodeSingularUInt64Field(value: &self.feeAmount) }() + case 9: try { try decoder.decodeSingularUInt32Field(value: &self.swapVersion) }() default: break } } @@ -4806,6 +4817,9 @@ extension Ocp_Transaction_V1_StatefulSwapRequest.Initiate.CoinbaseStableSwapperC if self.feeAmount != 0 { try visitor.visitSingularUInt64Field(value: self.feeAmount, fieldNumber: 8) } + if self.swapVersion != 0 { + try visitor.visitSingularUInt32Field(value: self.swapVersion, fieldNumber: 9) + } try unknownFields.traverse(visitor: &visitor) } @@ -4818,6 +4832,7 @@ extension Ocp_Transaction_V1_StatefulSwapRequest.Initiate.CoinbaseStableSwapperC if lhs.fundingID != rhs.fundingID {return false} if lhs._destinationOwner != rhs._destinationOwner {return false} if lhs.feeAmount != rhs.feeAmount {return false} + if lhs.swapVersion != rhs.swapVersion {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -5486,7 +5501,7 @@ extension Ocp_Transaction_V1_StatelessSwapRequest.Initiate: SwiftProtobuf.Messag extension Ocp_Transaction_V1_StatelessSwapRequest.Initiate.CoinbaseStableSwapperClientParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = Ocp_Transaction_V1_StatelessSwapRequest.Initiate.protoMessageName + ".CoinbaseStableSwapperClientParameters" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}from_mint\0\u{3}to_mint\0\u{3}swap_amount\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}from_mint\0\u{3}to_mint\0\u{3}swap_amount\0\u{3}swap_version\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -5497,6 +5512,7 @@ extension Ocp_Transaction_V1_StatelessSwapRequest.Initiate.CoinbaseStableSwapper case 1: try { try decoder.decodeSingularMessageField(value: &self._fromMint) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._toMint) }() case 3: try { try decoder.decodeSingularUInt64Field(value: &self.swapAmount) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self.swapVersion) }() default: break } } @@ -5516,6 +5532,9 @@ extension Ocp_Transaction_V1_StatelessSwapRequest.Initiate.CoinbaseStableSwapper if self.swapAmount != 0 { try visitor.visitSingularUInt64Field(value: self.swapAmount, fieldNumber: 3) } + if self.swapVersion != 0 { + try visitor.visitSingularUInt32Field(value: self.swapVersion, fieldNumber: 4) + } try unknownFields.traverse(visitor: &visitor) } @@ -5523,6 +5542,7 @@ extension Ocp_Transaction_V1_StatelessSwapRequest.Initiate.CoinbaseStableSwapper if lhs._fromMint != rhs._fromMint {return false} if lhs._toMint != rhs._toMint {return false} if lhs.swapAmount != rhs.swapAmount {return false} + if lhs.swapVersion != rhs.swapVersion {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/ocp.lock b/ocp.lock index 25e7458..eb37ea5 100644 --- a/ocp.lock +++ b/ocp.lock @@ -1,4 +1,4 @@ # Pinned upstream contract. Regenerate with scripts/sync-protos.sh . upstream: code-payments/ocp-protobuf-api -commit: 6f70c0d8e832531fd006bbb02fcb2c5d07e0c247 -subject: Add SendPublicMultiPaymentMetadata intent type (#70) +commit: 98cf14de185d5abadf6f03b6802ff298f61610d0 +subject: Enable clients to migrate to Coinbase Stableswapper's v2 swap instruction (#71) diff --git a/proto/transaction/v1/ocp_transaction_service.proto b/proto/transaction/v1/ocp_transaction_service.proto index b222348..c4937bc 100644 --- a/proto/transaction/v1/ocp_transaction_service.proto +++ b/proto/transaction/v1/ocp_transaction_service.proto @@ -405,6 +405,11 @@ message StatefulSwapRequest { // The fee amount to pay for this swap, which should be exactly // CanWithdrawToAccountResponse.fee_amount uint64 fee_amount = 8; + + // The CoinbaseStableSwapper swap instruction version the client will + // use when constructing the transaction. 2 uses SwapV2. Any other value, + // including unset, uses the legacy Swap instruction. + uint32 swap_version = 9 [(validate.rules).uint32.lte = 2]; } // The owner account starting the swap @@ -655,7 +660,7 @@ message StatefulSwapResponse { // 5. AssociatedTokenAccount::CreateIdempotent (open swap authority's from_mint ATA) // 6. AssociatedTokenAccount::CreateIdempotent (open destination owner's to_mint ATA) // 7. VM::TransferForSwapWithFee (from_mint VM swap ATA -> swap authority's from_mint ATA (swap amount) and fee destination (fee amount)) - // 8. CoinbaseStableSwapper::Swap (from_mint swap authority ATA -> to_mint destination owner ATA) + // 8. CoinbaseStableSwapper::Swap or CoinbaseStableSwapper::SwapV2, per client swap_version (from_mint swap authority ATA -> to_mint destination owner ATA) // 9. Token::CloseAccount (closes swap authority's from_mint ATA) message CoinbaseStableSwapperServerParameter { // Subisdizer account that will be paying for the swap @@ -741,6 +746,11 @@ message StatelessSwapRequest { // The amount to swap from the source mint in quarks. uint64 swap_amount = 3 [(validate.rules).uint64.gt = 0]; + + // The CoinbaseStableSwapper swap instruction version the client will + // use when constructing the transaction. 2 uses SwapV2. Any other value, + // including unset, uses the legacy Swap instruction. + uint32 swap_version = 4 [(validate.rules).uint32.lte = 2]; } // The owner account that owns the source ATA and the destination VM @@ -794,7 +804,7 @@ message StatelessSwapResponse { // 2. [Optional] ComputeBudget::SetComputeUnitPrice // 3. [Optional] Memo::Memo // 4. AssociatedTokenAccount::CreateIdempotent (open owner's to_mint VM Deposit ATA) - // 5. CoinbaseStableSwapper::Swap (owner's from_mint ATA -> owner's to_mint VM Deposit ATA) + // 5. CoinbaseStableSwapper::Swap or CoinbaseStableSwapper::SwapV2, per client swap_version (owner's from_mint ATA -> owner's to_mint VM Deposit ATA) message CoinbaseStableSwapperServerParameter { // Subsidizer account that will pay the transaction fee. common.v1.SolanaAccountId payer = 1 [(validate.rules).message.required = true]; From 2118bc029564399ae47ba7ea2703accfb106bca2 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Thu, 22 Oct 2026 11:08:20 -0400 Subject: [PATCH 2/2] docs(changelog): add 0.9.0 entry for swap_version --- CHANGELOG.md | 30 ++++++++++++++++++++ Sources/OCPClientProtocol/ContractInfo.swift | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec2e987..35c235a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,36 @@ called out explicitly even when nothing else did. release notes, so a version with no entry here does not release. Write the entry in the same PR that syncs the contract, while the diff is still in front of you. +## 0.9.0 + +Synced to [`ocp-protobuf-api@98cf14de`](https://github.com/code-payments/ocp-protobuf-api/commit/98cf14de185d5abadf6f03b6802ff298f61610d0), +picking up [#71](https://github.com/code-payments/ocp-protobuf-api/pull/71). Only `transaction.v1` moved. + +Stablecoin swaps can now use CoinbaseStableSwapper's `swap_v2` instruction, which is `swap` without +the deprecated whitelist account. The field is additive, so upgrading changes nothing until a client +sets it. + +### Added + +- `swap_version` (`uint32`, at most 2) on the CoinbaseStableSwapper initiate parameters of + `StatefulSwapRequest` (field 9) and `StatelessSwapRequest` (field 4). Only `2` selects `swap_v2`. + Unset, `0` and `1` keep the legacy `swap` instruction. +- The server builds the swap instruction from this field, and the client rebuilds and signs the + same transaction locally. A client that sends `2` has to build `swap_v2` itself: discriminator + `[43, 4, 237, 11, 26, 201, 30, 98]`, the same `amount_in` and `min_amount_out` args, and `swap`'s + accounts with the whitelist (index 12) removed. If the field and the local instruction disagree, + the swap fails. + +### Changed + +- Comments only: the CoinbaseStableSwapper server parameters on `StatefulSwapResponse` and + `StatelessSwapResponse` now list the swap step as `Swap` or `SwapV2`, per `swap_version`. + +### Unchanged + +Nothing was renumbered, and nothing existing changed type or meaning. No service, RPC, message or +enum case was removed. + ## 0.8.0 Synced to [`ocp-protobuf-api@6f70c0d8`](https://github.com/code-payments/ocp-protobuf-api/commit/6f70c0d8e832531fd006bbb02fcb2c5d07e0c247), diff --git a/Sources/OCPClientProtocol/ContractInfo.swift b/Sources/OCPClientProtocol/ContractInfo.swift index 5d04b2f..81dc295 100644 --- a/Sources/OCPClientProtocol/ContractInfo.swift +++ b/Sources/OCPClientProtocol/ContractInfo.swift @@ -1,7 +1,7 @@ // Generated by scripts/generate-swift.sh. Do not edit. public enum OCPContractInfo { - public static let version = "0.8.0-dev" + public static let version = "0.9.0-dev" public static let protoCommit = "98cf14de185d5abadf6f03b6802ff298f61610d0" public static var isLocal: Bool { protoCommit == localSentinel }