From eec80527a83da6053bf4553ab8a510696f821fcc Mon Sep 17 00:00:00 2001 From: Pierre Michard Date: Wed, 19 Aug 2026 10:04:08 +0200 Subject: [PATCH] Document the Solana signing flow for marketplace offers The marketplace sections of the README described the AuthorizationRequest flow in StarkEx terms only, so the Solana and Base examples were undiscoverable when building offers, and nothing said where the Solana signing key comes from. - Add an "Authorization request types" table mapping each request type to its example and approval field, and cross-reference it from "Bidding on auction", "Creating offers" and "Accepting offers". - Document deriving the Solana key pair from the exported Sorare private key (SLIP-0010, m/44'/501'/0'/0'), with a new solanaKeyPair.js example. The derived address is the request's senderAddress, which is the quickest way to check the derivation. - Spell out the signed message, and the three silent failure modes: signing the string instead of the SHA-256 hash, and assuming assetId or senderAddress are part of the message. - State that @sorare/crypto is StarkEx-only. - Fix solanaTokenTransfer.js, which built a solanaBankTransferApproval. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 10 ++++ README.md | 78 ++++++++++++++++++++++++++++ examples/package.json | 1 + examples/solanaBankTransfer.js | 7 +++ examples/solanaKeyPair.js | 55 ++++++++++++++++++++ examples/solanaTokenTransfer.js | 91 ++++++++++++++++++++++----------- examples/yarn.lock | 40 +++++++++++++++ web3/README.md | 4 +- 8 files changed, 254 insertions(+), 32 deletions(-) create mode 100644 examples/solanaKeyPair.js diff --git a/CHANGELOG.md b/CHANGELOG.md index aec407e..506d93f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to the Sorare GraphQL API will be documented in this file. W The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## 2026-08-19 + +Documentation only, no API change. + +The marketplace sections of the README described the `AuthorizationRequest` flow in StarkEx terms only, which made the Solana and Base examples added on 2025-11-04 hard to find when building offers. The README now states that `prepareBid`, `prepareOffer` and `prepareAcceptOffer` return different authorization request types depending on the asset and the payment rail, and lists them in a table mapping each type to its example and its approval field. The "Bidding on auction", "Creating offers" and "Accepting offers" sections point at it. + +We also documented how to obtain the Solana key pair used to sign Solana authorization requests: it is derived from the Sorare (Ethereum) private key you export from your wallet, using SLIP-0010 on the standard Solana path `m/44'/501'/0'/0'`. A new example, `solanaKeyPair.js`, performs that derivation; the derived address is the `senderAddress` of the authorization request, which is the quickest way to check it. Note that `@sorare/crypto` supports StarkEx only and cannot sign Solana or Base requests. + +`solanaTokenTransfer.js` now builds its `solanaTokenTransferApproval` (it previously showed `solanaBankTransferApproval` by mistake) and derives its signing key rather than assuming you already hold one. + ## 2025-11-04 Starting this Thursday some tokens will migrate to Solana and it will be possible to pay using SOL. We've added three examples to help you build approvals if you receive Solana or Base authorization requests from the `prepareBid`, `prepareOffer` and `prepareAcceptOffer` mutations: diff --git a/README.md b/README.md index 30f2325..8e40a1d 100644 --- a/README.md +++ b/README.md @@ -437,6 +437,78 @@ Every operation that involves card or money transfer must be signed with your St To sign with your Starkware _private key_ in JavaScript, we recommend using the JavaScript package [`@sorare/crypto`](https://github.com/sorare/crypto). +### Authorization request types + +The `prepareBid`, `prepareOffer` and `prepareAcceptOffer` mutations return **different types of `AuthorizationRequest` depending on the asset and on the payment rail**. A card that lives on Solana produces a Solana request; a payment in SOL produces another; a payment in ETH on Base produces another still. You must branch on the `__typename` of each returned request and build the matching approval — there is no single signing routine that covers them all. + +| `AuthorizationRequest` type | Used for | Example | Approval field | +| --- | --- | --- | --- | +| `StarkexTransferAuthorizationRequest` | Legacy StarkEx card & fund transfers | [authorizations.js](./examples/authorizations.js) | `starkexTransferApproval` | +| `StarkexLimitOrderAuthorizationRequest` | Legacy StarkEx limit orders | [authorizations.js](./examples/authorizations.js) | `starkexLimitOrderApproval` | +| `MangopayWalletTransferAuthorizationRequest` | Fiat wallet payments | [authorizations.js](./examples/authorizations.js) | `mangopayWalletTransferApproval` | +| `SolanaTokenTransferAuthorizationRequest` | Card (NFT) transfer on Solana | [solanaTokenTransfer.js](./examples/solanaTokenTransfer.js) | `solanaTokenTransferApproval` | +| `SolanaBankTransferAuthorizationRequest` | Payment in SOL | [solanaBankTransfer.js](./examples/solanaBankTransfer.js) | `solanaBankTransferApproval` | +| `EthereumBankTransferAuthorizationRequest` | Payment in ETH on Base | [baseBankTransfer.js](./examples/baseBankTransfer.js) | `ethereumBankTransferApproval` | + +Every approval is submitted alongside the `fingerprint` of the request it answers. + +Note that [`@sorare/crypto`](https://github.com/sorare/crypto) **supports StarkEx only** — it contains a single StarkEx signature implementation and does not help for Solana or Base. `buildApprovals` in [authorizations.js](./examples/authorizations.js) is likewise StarkEx-only. For Solana requests, sign with `@solana/kit` as shown in the examples above; for Base requests, sign with `viem`. + +Player Cards are minted on Solana as [Metaplex Bubblegum v2](https://developers.metaplex.com/bubblegum-v2) compressed NFTs and are moved by Sorare's Transfer Proxy program. See [web3/README.md](./web3/README.md) for the programs, contracts and collections involved. + +### Signing Solana authorization requests + +Solana requests are **not** signed with your Starkware private key. They are signed with your Solana key pair, which is derived from the Sorare (Ethereum) private key you export from your wallet: + +- SLIP-0010 HD derivation, using the Ethereum private key bytes as the master seed +- derivation path `m/44'/501'/0'/0'` (the standard Solana path) +- an ed25519 key pair built from the derived private key bytes + +A working JavaScript code sample is available in [examples/solanaKeyPair.js](./examples/solanaKeyPair.js). + +The address of the derived key pair is the `senderAddress` of the authorization request. **Checking the derived address against `senderAddress` is the fastest way to confirm your derivation** before you start debugging signatures. + +Once you hold the key pair, signing a `SolanaTokenTransferAuthorizationRequest` means building this exact message from the request: + +```js +const message = [ + 'TRANSFER', + transferProxyProgramAddress, + merkleTreeAddress, + leafIndex.toString(), + nonce, + expirationTimestamp.toString(), + receiverAddress, + '0x', + originator, +].join(':'); +``` + +then UTF-8 encoding it, hashing it with SHA-256, signing the resulting 32-byte hash with ed25519, and Base58-encoding the signature. + +Three things are easy to get wrong here, and each of them produces a well-formed signature that is silently and always rejected: + +- you sign the **SHA-256 hash**, not the message string +- `assetId` is **not** part of the signed message — the card is identified on chain by `merkleTreeAddress` and `leafIndex` — even though `assetId` is returned in the request +- `senderAddress` is **not** part of the signed message either, since it is implied by the signing key, while `transferProxyProgramAddress` and `originator` **are** + +The `'0x'` entry is a literal empty data field, not a placeholder to substitute. + +The resulting approval has exactly three fields. `nonce` and `expirationTimestamp` are echoed back unchanged from the request, because both are part of the signed message: + +```js +const approval = { + fingerprint: solanaTokenTransferAuthorizationRequest.fingerprint, + solanaTokenTransferApproval: { + signature, // Base58 string + nonce, // String holding a uint32 + expirationTimestamp, // Int, unix seconds + }, +}; +``` + +A working JavaScript code sample is available in [examples/solanaTokenTransfer.js](./examples/solanaTokenTransfer.js). + ### Listing auctions To list the latest auctions, you can use the following query: @@ -528,6 +600,8 @@ ${authorizationRequestFragment} 3. Sign all `AuthorizationRequest` objects and build the `bidInput` argument. `buildApprovals` is defined in [authorizations.js](./examples/authorizations.js). +`prepareBid` returns different types of `AuthorizationRequest` depending on the payment rail, and `buildApprovals` only handles the StarkEx and Mangopay ones. Branch on the `__typename` of each request and see [Authorization request types](#authorization-request-types) for the full list — in particular [solanaBankTransfer.js](./examples/solanaBankTransfer.js) if you pay in SOL, and [baseBankTransfer.js](./examples/baseBankTransfer.js) if you pay in ETH on Base. + ```js const approvals = buildApprovals(starkPrivateKey, authorizations); @@ -629,6 +703,8 @@ ${authorizationRequestFragment} 3. Sign all `AuthorizationRequest` objects and build the `createSingleSaleOfferInput` or `createDirectOfferInput` argument. `buildApprovals` is defined in [authorizations.js](./examples/authorizations.js). +`prepareOffer` returns different types of `AuthorizationRequest` depending on where the card lives and on the payment rail, and `buildApprovals` only handles the StarkEx and Mangopay ones. Branch on the `__typename` of each request and see [Authorization request types](#authorization-request-types) for the full list. If the card you are sending is on Solana — which is the case for both `SINGLE_SALE_OFFER` and `DIRECT_OFFER` on migrated cards — you will get a `SolanaTokenTransferAuthorizationRequest`, signed as described in [Signing Solana authorization requests](#signing-solana-authorization-requests) and shown in [solanaTokenTransfer.js](./examples/solanaTokenTransfer.js). Payments produce a `SolanaBankTransferAuthorizationRequest` ([solanaBankTransfer.js](./examples/solanaBankTransfer.js)) or an `EthereumBankTransferAuthorizationRequest` ([baseBankTransfer.js](./examples/baseBankTransfer.js)). + ```js const approvals = buildApprovals(starkPrivateKey, authorizations); @@ -748,6 +824,8 @@ ${authorizationRequestFragment} 4. Sign all `AuthorizationRequest` objects and build the `acceptOfferInput` argument. `buildApprovals` is defined in [authorizations.js](./examples/authorizations.js). +`prepareAcceptOffer` returns different types of `AuthorizationRequest` depending on where the card lives and on the payment rail, and `buildApprovals` only handles the StarkEx and Mangopay ones. Branch on the `__typename` of each request and see [Authorization request types](#authorization-request-types) for the full list. Accepting a direct offer where you send a card on Solana yields a `SolanaTokenTransferAuthorizationRequest` ([solanaTokenTransfer.js](./examples/solanaTokenTransfer.js), and [Signing Solana authorization requests](#signing-solana-authorization-requests)); paying yields a `SolanaBankTransferAuthorizationRequest` ([solanaBankTransfer.js](./examples/solanaBankTransfer.js)) or an `EthereumBankTransferAuthorizationRequest` ([baseBankTransfer.js](./examples/baseBankTransfer.js)). + ```js const approvals = buildApprovals(starkPrivateKey, authorizations); diff --git a/examples/package.json b/examples/package.json index 7a1739c..9fb3e73 100644 --- a/examples/package.json +++ b/examples/package.json @@ -7,6 +7,7 @@ "crypto": "^1.0.1", "graphql": "^16.2.0", "graphql-request": "^3.7.0", + "micro-key-producer": "^0.10.0", "viem": "^2.38.6", "yargs": "^17.3.0" } diff --git a/examples/solanaBankTransfer.js b/examples/solanaBankTransfer.js index 473da89..821f39f 100644 --- a/examples/solanaBankTransfer.js +++ b/examples/solanaBankTransfer.js @@ -1,3 +1,10 @@ +// Signing a `SolanaBankTransferAuthorizationRequest`, the authorization request +// you get when paying in SOL. +// +// This example starts from a Solana private key you already hold. See +// solanaKeyPair.js for how to derive that key pair from the Sorare private key +// exported from your wallet. + const { createKeyPairFromBytes, createSignerFromKeyPair, diff --git a/examples/solanaKeyPair.js b/examples/solanaKeyPair.js new file mode 100644 index 0000000..e57dd2f --- /dev/null +++ b/examples/solanaKeyPair.js @@ -0,0 +1,55 @@ +// Deriving your Solana key pair from your Sorare private key. +// +// Solana authorization requests (`SolanaTokenTransferAuthorizationRequest` and +// `SolanaBankTransferAuthorizationRequest`) must be signed with your Solana key +// pair. You never export that key pair from sorare.com directly: it is derived +// deterministically from the Sorare (Ethereum) private key you export from your +// wallet (see the "Examples" section of the top-level README). +// +// The derivation is standard SLIP-0010: +// - the Ethereum private key bytes are used as the HD master seed +// - the derivation path is m/44'/501'/0'/0' (the standard Solana path) +// - the derived private key bytes give an ed25519 key pair +// +// The address of the derived key pair is the `senderAddress` returned in the +// authorization request. Comparing the two is the fastest way to confirm your +// derivation is correct before you start debugging signatures. + +const { + createKeyPairFromPrivateKeyBytes, + createSignerFromKeyPair, +} = require('@solana/kit'); +const { HDKey } = require('micro-key-producer/slip10.js'); + +const SOLANA_DERIVATION_PATH = "m/44'/501'/0'/0'"; + +// `ethereumPrivateKey` is the private key exported from sorare.com, with or +// without the leading `0x`. +const deriveSolanaSigner = async ethereumPrivateKey => { + const seed = Buffer.from(ethereumPrivateKey.replace(/^0x/, ''), 'hex'); + const { privateKey: derivedPrivateKeyBytes } = HDKey.fromMasterSeed( + seed + ).derive(SOLANA_DERIVATION_PATH); + + const keyPair = await createKeyPairFromPrivateKeyBytes( + derivedPrivateKeyBytes + ); + + return createSignerFromKeyPair(keyPair); +}; + +module.exports = { deriveSolanaSigner, SOLANA_DERIVATION_PATH }; + +// Running this file prints the derived Solana address. Check it against the +// `senderAddress` of the authorization request you are trying to sign: if they +// differ, the problem is the derivation, not the signature. +if (require.main === module) { + const ethereumPrivateKey = + '0xa9405b77d085276e4b6e35cf494e83f0533d4751fc13e2fdceb6229330ef5146'; + + deriveSolanaSigner(ethereumPrivateKey).then(signer => { + // Prints 8ixw6XQW2tuZhc1xgbhh6bq6YvL5K5nXLsN9LjrzMrxq, which is the + // `senderAddress` of the request in solanaBankTransfer.js. + console.log(signer.address); + }); +} diff --git a/examples/solanaTokenTransfer.js b/examples/solanaTokenTransfer.js index f40a9fb..597d00a 100644 --- a/examples/solanaTokenTransfer.js +++ b/examples/solanaTokenTransfer.js @@ -1,38 +1,61 @@ +// Signing a `SolanaTokenTransferAuthorizationRequest`. +// +// This is the authorization request you get from `prepareOffer` (SINGLE_SALE_OFFER +// and DIRECT_OFFER) and from `prepareAcceptOffer` when the card you are sending +// lives on Solana. It authorises the Transfer Proxy program to move one Player +// Card, and it is signed with your Solana key pair — not with your Starkware key +// and not with `@sorare/crypto`, which only supports StarkEx. +// +// See solanaKeyPair.js for where the Solana key pair comes from. + const { - createKeyPairFromBytes, - createSignerFromKeyPair, createSignableMessage, - getBase58Encoder, getBase58Decoder, } = require('@solana/kit'); +const { deriveSolanaSigner } = require('./solanaKeyPair'); -const privateKey = '2KGrum1o5ZudshxeUDjKesA5hvyGvHeqaUet6BMUhb8zi7eCT9ifgCBFYWYTn2o8oM5js2FCs2aHj6ABDLfP8vaA' +// The Sorare private key exported from your wallet on sorare.com. +const ethereumPrivateKey = + '0xa9405b77d085276e4b6e35cf494e83f0533d4751fc13e2fdceb6229330ef5146'; const solanaTokenTransferAuthorizationRequest = { - __typename: "AuthorizationRequest", - fingerprint: "d4d0f9558d2f58cad7ebbed5a92edc49", + __typename: 'AuthorizationRequest', + fingerprint: 'd4d0f9558d2f58cad7ebbed5a92edc49', request: { - __typename: "SolanaTokenTransferAuthorizationRequest", - leafIndex: 5, - merkleTreeAddress: "CS7kYFjkSW9iPmCZpmNv5jwyE9FmLzR95ag2bpwtM8uF", - originator: "Dv8A8XKBz5QARFKZ5Kewdk8myCDcne9wiD7ULTanHKU", - receiverAddress: "cZq5d4nCqUJoysDh49TPRBSXgFx5dsP9Ho4PVJgYEDY", - expirationTimestamp: 1763482762, - nonce: "3", - transferProxyProgramAddress: "Gz9o1yxV5kVfyC53fFu7StTVeetPZWa2sohzvxJiLxMP" - } -} + __typename: 'SolanaTokenTransferAuthorizationRequest', + assetId: + '0x04002c8934c7fadd5a832a693b8a9d295a915fb1d0c2250d824ae18e7c5bba7a', + leafIndex: 5, + merkleTreeAddress: 'CS7kYFjkSW9iPmCZpmNv5jwyE9FmLzR95ag2bpwtM8uF', + originator: 'Dv8A8XKBz5QARFKZ5Kewdk8myCDcne9wiD7ULTanHKU', + receiverAddress: 'cZq5d4nCqUJoysDh49TPRBSXgFx5dsP9Ho4PVJgYEDY', + senderAddress: '8ixw6XQW2tuZhc1xgbhh6bq6YvL5K5nXLsN9LjrzMrxq', + expirationTimestamp: 1763482762, + nonce: '3', + transferProxyProgramAddress: 'Gz9o1yxV5kVfyC53fFu7StTVeetPZWa2sohzvxJiLxMP', + }, +}; const { leafIndex, merkleTreeAddress, originator, receiverAddress, + senderAddress, expirationTimestamp, nonce, transferProxyProgramAddress, } = solanaTokenTransferAuthorizationRequest.request; +// Note what is, and is not, part of the signed message: +// - `assetId` is NOT signed. The card is identified on chain by +// `merkleTreeAddress` + `leafIndex`, even though `assetId` is returned in the +// request so you can tell which card it is. +// - `senderAddress` is NOT signed either: it is implied by the signing key. +// - `transferProxyProgramAddress` and `originator` ARE signed. +// - `'0x'` is a literal empty data field. It is not a placeholder to substitute. +// Getting any of this wrong produces a well-formed signature that is always +// rejected, with no clue as to why. const message = [ 'TRANSFER', transferProxyProgramAddress, @@ -49,27 +72,35 @@ const textEncoder = new TextEncoder(); const messageBytes = textEncoder.encode(message); async function signRequest() { - const secretKeyBytes = getBase58Encoder().encode(privateKey) - const keyPair = await createKeyPairFromBytes(secretKeyBytes) - const signer = await createSignerFromKeyPair( - keyPair - ); + const signer = await deriveSolanaSigner(ethereumPrivateKey); + + // The derived address is the `senderAddress` of the request. If this throws, + // your derivation is wrong and there is no point debugging the signature. + if (signer.address !== senderAddress) { + throw new Error( + `Derived ${signer.address} but the request is for ${senderAddress}` + ); + } + + // You sign the SHA-256 hash of the message, not the message itself. const messageHash = await crypto.subtle.digest('SHA-256', messageBytes); - const mess = createSignableMessage(new Uint8Array(messageHash)); - const [ret] = await signer.signMessages([mess]); - const signature = getBase58Decoder().decode(ret['BvJrHm3rBx9ddmz4dzK4Jp8ibC8WSfYP5qipE7M1CbDx']); + const signableMessage = createSignableMessage(new Uint8Array(messageHash)); + const [signatures] = await signer.signMessages([signableMessage]); + const signature = getBase58Decoder().decode(signatures[signer.address]); + // `nonce` and `expirationTimestamp` are echoed back unchanged from the + // request: both are part of the signed message, so any other value invalidates + // the signature. const approval = { fingerprint: solanaTokenTransferAuthorizationRequest.fingerprint, - solanaBankTransferApproval: { - signature, - expirationTimestamp, - nonce, + solanaTokenTransferApproval: { + signature, // Base58 string + nonce, // String holding a uint32 + expirationTimestamp, // Int, unix seconds }, }; console.log(approval); } -// expected signature: 4aR7f1eaVbBfxQze5vgktZ18RP1tUYB28yiaRTBJDPgtpe4kcvAcMq3QM6C8HPTzVTS9RYxj9XdrwmGnpU52Fc5n -signRequest(); \ No newline at end of file +signRequest(); diff --git a/examples/yarn.lock b/examples/yarn.lock index 335fd5c..063b8fe 100644 --- a/examples/yarn.lock +++ b/examples/yarn.lock @@ -12,6 +12,11 @@ resolved "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== +"@noble/ciphers@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-2.3.0.tgz#3bcea439b4c90f1cd32c8f7d647d1aebedc4d4d9" + integrity sha512-Clu/xdfgVTf9o7ngLOURaxePwR0j8sjclKEtVij10/jGulwFsPWCvvRgG/XjUVf8Nei+jLG6uwyXzUTGY1DQrw== + "@noble/curves@1.9.1": version "1.9.1" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz#9654a0bc6c13420ae252ddcf975eaf0f58f0a35c" @@ -26,6 +31,13 @@ dependencies: "@noble/hashes" "1.8.0" +"@noble/curves@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-2.3.0.tgz#0ac09b0c0fc3ceffbbe599c2e5685b5f59ab005d" + integrity sha512-v7cY+4oWYPQszRj6ZFGzTVL7uP2TaLo1xMhWHzYC5wj0ZhOXQ5x+sBre8rF3hi8cAoi0bh1qXoovoOkdFtvqEg== + dependencies: + "@noble/hashes" "2.3.0" + "@noble/curves@~1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@noble/curves/-/curves-1.0.0.tgz#e40be8c7daf088aaf291887cbc73f43464a92932" @@ -43,11 +55,21 @@ resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== +"@noble/hashes@2.3.0", "@noble/hashes@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-2.3.0.tgz#505fd39c3134a37e67c8c4e6c6049a496154879c" + integrity sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ== + "@noble/hashes@~1.3.0": version "1.3.3" resolved "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== +"@scure/base@^2.3.0", "@scure/base@~2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-2.3.0.tgz#fc7acb5b7084c53e4e2537d83fa72cddac7143fd" + integrity sha512-NsG6Y03tY6R5BUis4FdVtHVkur0U6FOzskgs9ZXNl78CUc9fkZ78HmENUle1nSOkCasDmbubmWD9qwB7mm4PZA== + "@scure/base@~1.1.0": version "1.1.9" resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz#e5e142fbbfe251091f9c5f1dd4c834ac04c3dbd1" @@ -631,6 +653,24 @@ isows@1.0.7: resolved "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz#1c06400b7eed216fbba3bcbd68f12490fc342915" integrity sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg== +micro-key-producer@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/micro-key-producer/-/micro-key-producer-0.10.0.tgz#e13db16aa43622ab06fd189ca5f0167cc2907746" + integrity sha512-qlx+gTP4IWgWBsWAmTp04iF/ovYWjUtr+ZeDRL25N5G4hOvr/aCWO78uW9QOlck2AizPE6RwrFt32u6fvFF0CQ== + dependencies: + "@noble/ciphers" "^2.3.0" + "@noble/curves" "^2.3.0" + "@noble/hashes" "^2.3.0" + "@scure/base" "^2.3.0" + micro-packed "^0.11.0" + +micro-packed@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/micro-packed/-/micro-packed-0.11.0.tgz#3552730762a384cf952ebec9a3adfacd1c3dd515" + integrity sha512-rq9RzPpmlgt3YaW2RrtCG9Q2iydX2+KZnL/Vhik8KKdsKwR5FvLXJzqp5IhXqD67XjYeZEMCrG6B28Mq9uC8Tw== + dependencies: + "@scure/base" "~2.3.0" + micro-starknet@0.2.3: version "0.2.3" resolved "https://registry.npmjs.org/micro-starknet/-/micro-starknet-0.2.3.tgz#ff4e7caf599255d2110e9c57bb483dfaf493ccb3" diff --git a/web3/README.md b/web3/README.md index 399c4f4..b1daa9f 100644 --- a/web3/README.md +++ b/web3/README.md @@ -4,7 +4,7 @@ Player Cards are NFTs. They are minted on Solana. Due to the amount of NFTs that are minted on any given season we use [Metaplex Bubblegum v2](https://developers.metaplex.com/bubblegum-v2) compressed NFT standard. Each sport (football, NBA, MLB) has its own collection for every single season (e.g. [NBA 2025 collection](https://magiceden.io/marketplace/sorare_nba_2025)). The list of collections can be found in `./constants/solana.js`. -Sorare has deployed a Transfer Proxy program on Solana. This programs allows Sorare to transfer an NFT from one manager to another if, and only if, Sorare is able to provide a cryptographic signature from the NFT owner. This cryptographic signature has an expiry and can't be replayed. You can see how the signature is produced here: [solanaTokenTransfer.js](../examples/solanaTokenTransfer.js). This program has been audited by Halborn. +Sorare has deployed a Transfer Proxy program on Solana. This programs allows Sorare to transfer an NFT from one manager to another if, and only if, Sorare is able to provide a cryptographic signature from the NFT owner. This cryptographic signature has an expiry and can't be replayed. You can see how the signature is produced here: [solanaTokenTransfer.js](../examples/solanaTokenTransfer.js), and how to derive the Solana key pair that produces it from your exported Sorare private key here: [solanaKeyPair.js](../examples/solanaKeyPair.js). This program has been audited by Halborn. * Address [`Gz9o1yxV5kVfyC53fFu7StTVeetPZWa2sohzvxJiLxMP`](https://solscan.io/account/Gz9o1yxV5kVfyC53fFu7StTVeetPZWa2sohzvxJiLxMP) * Authority `6U34EJ79yNBLyHUgMsPmQJag4fgKMfNxENZZQgio32cF` @@ -29,7 +29,7 @@ The high level API of Bank is fairly simple: * `deposit` allows a manager to deposit funds in the Bank under their address * `exit` allows a manager to withdraw all funds from the Bank to any address * `withdraw` allows Sorare to withdraw available funds of a manager to any address. It requires a cryptographic signature from the withdrawer. -* `transfer` allows Sorare to move funds between accounts within the Bank. It requires a cryptographic signature from the payer. You can see how signatures are produced in [baseBankTransfer.js](../examples/baseBankTransfer.js) and [solanaBankTransfer.js](../examples/solanaBankTransfer.js) +* `transfer` allows Sorare to move funds between accounts within the Bank. It requires a cryptographic signature from the payer. You can see how signatures are produced in [baseBankTransfer.js](../examples/baseBankTransfer.js) and [solanaBankTransfer.js](../examples/solanaBankTransfer.js). Solana signatures use the key pair derived in [solanaKeyPair.js](../examples/solanaKeyPair.js) Our Bank contracts have been audited by Halborn and are verified on chain: * Base chain Bank contract: [`0x7473899213aa6A3d321eCC2259F567EF1Af2acb8`](https://basescan.org/address/0x7473899213aa6A3d321eCC2259F567EF1Af2acb8)